# -*- coding: utf-8 -*-
"""
Windows Process Spy - 窗口句柄检测工具
用于检测和分析Windows窗口句柄，识别其他程序的窗口和控件
"""

# 在任何tkinter导入之前设置Tcl/Tk环境变量
import sys
import os
from modules.tkinter_setup import setup_tcltk_environment
setup_tcltk_environment()

import win32gui
import win32con
import win32api
import win32process
import win32ts
import psutil
import tkinter as tk
from tkinter import ttk, messagebox, scrolledtext, filedialog
import threading
import time
import os
import subprocess
import json
from typing import List, Dict, Tuple, Optional
import sys
from datetime import datetime

# 从模块导入
from modules.core import ConfigManager
from modules.ui import MenuManager, TrayManager, SettingsManager

# 设置日志文件（兼容打包后的exe路径）
if getattr(sys, 'frozen', False):
    _BASE_DIR = os.path.dirname(sys.executable)
else:
    _BASE_DIR = os.path.dirname(os.path.abspath(__file__))
LOG_FILE = os.path.join(_BASE_DIR, "screenshot_debug.log")
LOG_FOCUS = "ALL"  # 可选: "ALL" | "PICK"，PICK时仅输出拾取相关日志

def debug_log(msg):
    """记录调试日志到文件和控制台（可按类别过滤）"""
    if LOG_FOCUS == "PICK":
        allow_prefixes = ("[PICK]", "[WINDOW]", "[SKIP]", "[FOREGROUND]")
        if not str(msg).startswith(allow_prefixes):
            return
    timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]
    log_msg = f"[{timestamp}] {msg}"
    try:
        print(log_msg)
    except Exception:
        pass
    try:
        with open(LOG_FILE, 'a', encoding='utf-8') as f:
            f.write(log_msg + '\n')
    except Exception:
        pass


# ============================================================================
# 图标管理 (整合自 icon_embedded.py)
# ============================================================================
import base64
import io
from PIL import Image

_ICON_DATA_B64 = None

def _load_icon_data():
    """加载图标数据到内存"""
    global _ICON_DATA_B64
    if _ICON_DATA_B64 is None:
        icon_path = os.path.join(_BASE_DIR, "res", "icon.png")
        if os.path.exists(icon_path):
            try:
                with open(icon_path, 'rb') as f:
                    _ICON_DATA_B64 = base64.b64encode(f.read()).decode('utf-8')
            except Exception as e:
                debug_log(f"[ICON] Failed to load icon: {e}")
    return _ICON_DATA_B64

def get_icon_image():
    """获取图标图片对象（PIL Image），从内存中加载"""
    icon_data = _load_icon_data()
    if icon_data:
        try:
            icon_bytes = base64.b64decode(icon_data)
            return Image.open(io.BytesIO(icon_bytes))
        except Exception as e:
            debug_log(f"[ICON] Error decoding icon data: {e}")
            return None
    return None


class WindowInfo:
    """窗口信息类"""
    def __init__(self, hwnd: int, title: str, class_name: str, process_id: int, 
                 process_name: str, rect: Tuple[int, int, int, int], exe_path: str = ""):
        self.hwnd = hwnd
        self.title = title
        self.class_name = class_name
        self.process_id = process_id
        self.process_name = process_name
        self.exe_path = exe_path
        self.rect = rect
        self.controls = []
    
    def __str__(self):
        return f"{self.title} ({self.class_name}) - PID: {self.process_id}"


class ControlInfo:
    """控件信息类"""
    def __init__(self, hwnd: int, text: str, class_name: str, control_id: int, 
                 rect: Tuple[int, int, int, int], parent_hwnd: int):
        self.hwnd = hwnd
        self.text = text
        self.class_name = class_name
        self.control_id = control_id
        self.rect = rect
        self.parent_hwnd = parent_hwnd
    
    def __str__(self):
        return f"{self.text} ({self.class_name}) - ID: {self.control_id}"


class WindowSpy:
    """窗口检测核心类"""
    
    def __init__(self):
        self.windows = []
        self.is_monitoring = False
        self.monitor_thread = None
    
    def enum_windows_callback(self, hwnd: int, windows: List[WindowInfo]) -> bool:
        """窗口枚举回调函数"""
        try:
            # 检查窗口是否可见
            if not win32gui.IsWindowVisible(hwnd):
                return True
            
            # 获取窗口标题
            title = win32gui.GetWindowText(hwnd)
            if not title:  # 跳过无标题窗口
                return True
            
            # 获取窗口类名
            class_name = win32gui.GetClassName(hwnd)
            
            # 获取窗口矩形
            rect = win32gui.GetWindowRect(hwnd)
            
            # 获取进程ID
            _, process_id = win32process.GetWindowThreadProcessId(hwnd)
            
            # 获取进程名
            try:
                process = psutil.Process(process_id)
                process_name = process.name()
                exe_path = process.exe()
            except (psutil.NoSuchProcess, psutil.AccessDenied):
                process_name = "Unknown"
                exe_path = ""
            
            # 创建窗口信息对象
            window_info = WindowInfo(hwnd, title, class_name, process_id, 
                                   process_name, rect, exe_path)
            
            # 获取窗口的控件
            window_info.controls = self.get_window_controls(hwnd)
            
            windows.append(window_info)
            
        except Exception as e:
            print(f"枚举窗口时出错: {e}")
        
        return True
    
    def get_window_controls(self, parent_hwnd: int) -> List[ControlInfo]:
        """获取窗口的所有控件"""
        controls = []
        
        def enum_child_callback(hwnd: int, controls_list: List[ControlInfo]) -> bool:
            try:
                # 获取控件文本
                text = win32gui.GetWindowText(hwnd)
                
                # 获取控件类名
                class_name = win32gui.GetClassName(hwnd)
                
                # 获取控件ID
                control_id = win32gui.GetDlgCtrlID(hwnd)
                
                # 获取控件矩形
                rect = win32gui.GetWindowRect(hwnd)
                
                # 创建控件信息对象
                control_info = ControlInfo(hwnd, text, class_name, control_id, 
                                         rect, parent_hwnd)
                controls_list.append(control_info)
                
            except Exception as e:
                print(f"枚举控件时出错: {e}")
            
            return True
        
        # 枚举子窗口
        win32gui.EnumChildWindows(parent_hwnd, 
                                 lambda hwnd, param: enum_child_callback(hwnd, controls), 
                                 None)
        
        return controls
    
    def get_all_windows(self) -> List[WindowInfo]:
        """获取所有可见窗口"""
        windows = []
        win32gui.EnumWindows(self.enum_windows_callback, windows)
        return windows
    
    def get_window_by_title(self, title: str) -> Optional[WindowInfo]:
        """根据标题查找窗口"""
        windows = self.get_all_windows()
        for window in windows:
            if title.lower() in window.title.lower():
                return window
        return None
    
    def get_window_by_class(self, class_name: str) -> Optional[WindowInfo]:
        """根据类名查找窗口"""
        windows = self.get_all_windows()
        for window in windows:
            if class_name.lower() in window.class_name.lower():
                return window
        return None
    
    def get_window_by_process(self, process_name: str) -> List[WindowInfo]:
        """根据进程名查找窗口"""
        windows = self.get_all_windows()
        result = []
        for window in windows:
            if process_name.lower() in window.process_name.lower():
                result.append(window)
        return result
    
    def refresh_windows(self):
        """刷新窗口列表"""
        self.windows = self.get_all_windows()
    
    def start_monitoring(self, callback=None):
        """开始监控窗口变化"""
        if self.is_monitoring:
            return
        
        self.is_monitoring = True
        
        def monitor():
            while self.is_monitoring:
                try:
                    old_windows = self.windows.copy()
                    self.refresh_windows()
                    
                    # 检查窗口变化
                    if callback:
                        callback(self.windows, old_windows)
                    
                    time.sleep(1)  # 每秒检查一次
                except Exception as e:
                    print(f"监控过程中出错: {e}")
                    time.sleep(1)
        
        self.monitor_thread = threading.Thread(target=monitor, daemon=True)
        self.monitor_thread.start()
    
    def stop_monitoring(self):
        """停止监控"""
        self.is_monitoring = False
        if self.monitor_thread:
            self.monitor_thread.join(timeout=2)


class WindowSpyGUI:
    """窗口检测GUI界面"""
    
    def __init__(self):
        debug_log("GUI init start")
        
        # 退出信号文件路径（使用系统临时目录，避免在程序目录留下文件）
        import tempfile
        if getattr(sys, 'frozen', False):
            base_dir = os.path.dirname(sys.executable)
        else:
            base_dir = os.path.dirname(os.path.abspath(__file__))
        
        # 使用临时目录存放退出信号文件
        temp_dir = tempfile.gettempdir()
        self._exit_signal_file = os.path.join(temp_dir, "windowspygui_exit_signal.txt")
        self._base_dir = base_dir  # 保存base_dir供后续使用
        
        # 单例锁文件
        self._lock_file = os.path.join(base_dir, "windowspygui.lock")
        self._lock_handle = None
        
        # 检查是否已有实例在运行
        if not self._acquire_lock():
            debug_log("[EXIT] Singleton detected, exiting. Lock file: {}".format(self._lock_file))
            print("程序已在运行中，退出...")
            try:
                from tkinter import messagebox
                messagebox.showinfo("提示", "程序已在运行中，请勿重复启动。")
            except:
                pass
            sys.exit(0)
        
        self.spy = WindowSpy()
        self.root = tk.Tk()
        self.root.title("Windows Process Spy")
        self.root.geometry("1200x800")
        
        # 初始化标志:在初始化完成前禁用页签保存
        self._initializing = True
        
        # 设置窗口图标
        self._set_window_icon()
        
        # 任务管理相关变量
        self.task_processes = []  # 要监控的进程列表，格式：[{'name': 'xxx', 'type': 'name'/'pid'}]
        self.task_interval = 60   # 默认60秒检查一次
        self.task_running = False
        self.task_thread = None
        
        # 排序相关变量
        self.sort_column = None  # 当前排序列
        self.sort_reverse = False  # 排序方向
        
        # 配置管理器
        self.config = ConfigManager("window_spy_config.json")
        
        # 创建设置管理器
        self.settings_manager = SettingsManager(
            self.root, 
            self.config, 
            lambda: self._apply_hotkeys_from_config()
        )
        
        # 先创建menu_manager（不传入tray_manager）
        self.menu_manager = MenuManager(
            self.root, 
            self.config, 
            self.save_config, 
            lambda: self._apply_hotkeys_from_config()
        )
        
        # 然后创建tray_manager
        self.tray_manager = TrayManager(
            self.root,
            lambda: self._show_from_tray(),
            lambda: self._hide_to_tray(),
            self._base_dir,
            exit_callback=self.on_closing,
            settings_callback=self.settings_manager.show_settings
        )
        
        # 更新menu_manager的tray_manager引用
        self.menu_manager.tray_manager = self.tray_manager
        
        # 创建菜单
        self.menu_manager.create_menu_bar()
        
        # 设置UI界面（创建标签页等其他UI元素）
        self.setup_ui()
        
        self.load_config()  # 加载配置
        # 配置加载完成后，恢复上次选择的页签
        self.root.after(100, self._restore_tab_selection)
        self.refresh_window_list()
    
    def _set_window_icon(self):
        """设置窗口图标（从内存加载）"""
        try:
            from PIL import Image, ImageTk
            import io
            import base64
            
            # 方法1: 尝试从嵌入的图标数据加载
            img = None
            
            # 尝试从内存加载图标
            try:
                img = get_icon_image()
                if img:
                    debug_log("[ICON] Loaded icon from embedded data")
            except Exception as e:
                debug_log(f"[ICON] Failed to load from embedded data: {e}")
                icon_paths = [
                    os.path.join(self._base_dir, "res", "icon.png"),  # 开发环境
                ]
                
                # 打包后的路径（PyInstaller解压到临时目录）
                if getattr(sys, 'frozen', False):
                    if hasattr(sys, '_MEIPASS'):
                        icon_paths.insert(0, os.path.join(sys._MEIPASS, "res", "icon.png"))
                    icon_paths.insert(0, os.path.join(os.path.dirname(sys.executable), "res", "icon.png"))
                
                for path in icon_paths:
                    if os.path.exists(path):
                        img = Image.open(path)
                        debug_log(f"[ICON] Loaded icon from file: {path}")
                        break
            
            # 如果成功加载图标，设置窗口图标
            if img:
                photo = ImageTk.PhotoImage(img)
                self.root.iconphoto(True, photo)  # 设置窗口图标和任务栏图标
                # 保存引用避免被垃圾回收
                self._window_icon = photo
                debug_log("[ICON] Window icon set successfully")
            else:
                debug_log("[ICON] Failed to load icon image")
        except Exception as e:
            debug_log(f"[ICON] Error in _set_window_icon: {e}")
    
    def setup_ui(self):
        """设置用户界面"""
        # 不再创建menu_manager，因为已经在__init__中创建了
        
        # 创建主框架
        main_frame = ttk.Frame(self.root)
        main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
        
        # 创建页签控件
        self.notebook = ttk.Notebook(main_frame)
        self.notebook.pack(fill=tk.BOTH, expand=True)
        
        # 绑定页签切换事件，用于记忆最后选择的页签
        self.notebook.bind('<<NotebookTabChanged>>', self._on_tab_changed)
        
        # 第一个页签：通用功能
        self.setup_general_tab()
        
        # 第二个页签：任务管理
        self.setup_task_tab()

        # 第三个页签：截图
        self.setup_screenshot_tab()
        
        # 第四个页签：守护
        self.setup_guardian_tab()
        
        # 第五个页签：端口->进程
        self.setup_port_tab()
        
        # 注意：恢复页签选择在load_config之后进行（通过after延迟确保配置已加载）
    
    def _on_tab_changed(self, event=None):
        """页签切换事件处理，保存当前页签索引"""
        try:
            # 如果正在初始化，不保存
            if hasattr(self, '_initializing') and self._initializing:
                debug_log(f"[TAB] Skipping tab save (initializing)")
                return
            
            # 如果是程序自动切换（例如截图后），不保存
            if hasattr(self, '_skip_tab_save') and self._skip_tab_save:
                debug_log(f"[TAB] Skipping tab save (auto switch)")
                self._skip_tab_save = False
                return
            
            current_tab = self.notebook.index(self.notebook.select())
            debug_log(f"[TAB] Tab changed to: {current_tab}")
            if 0 <= current_tab < 5:
                self.config.set_last_tab(current_tab)
                # 保存配置
                result = self.config.save()
                debug_log(f"[TAB] Config saved: {result}, last_tab={current_tab}")
        except Exception as e:
            debug_log(f"[TAB] Error in _on_tab_changed: {e}")
    
    def _restore_tab_selection(self):
        """恢复上次选择的页签"""
        try:
            tab_index = self.config.get_last_tab()
            debug_log(f"[TAB] Restoring tab: {tab_index}")
            if 0 <= tab_index < 5:
                self._skip_tab_save = True  # 恢复页签时不保存
                self.notebook.select(tab_index)
                debug_log(f"[TAB] Tab restored to: {tab_index}")
        except Exception as e:
            debug_log(f"[TAB] Error restoring tab: {e}")
            # 如果恢复失败，使用默认页签（第一个）
            try:
                self._skip_tab_save = True
                self.notebook.select(0)
            except Exception:
                pass
        finally:
            # 初始化完成,启用页签保存功能
            self._initializing = False
            debug_log(f"[TAB] Initialization complete, tab save enabled")
    
    def setup_general_tab(self):
        """设置通用功能页签"""
        general_frame = ttk.Frame(self.notebook)
        self.notebook.add(general_frame, text="通用")
        
        # 创建工具栏
        toolbar = ttk.Frame(general_frame)
        toolbar.pack(fill=tk.X, pady=(0, 10))
        
        # 刷新按钮
        refresh_btn = ttk.Button(toolbar, text="刷新窗口列表", 
                               command=self.refresh_window_list)
        refresh_btn.pack(side=tk.LEFT, padx=(0, 5))
        
        # 监控按钮
        self.monitor_btn = ttk.Button(toolbar, text="开始监控", 
                                    command=self.toggle_monitoring)
        self.monitor_btn.pack(side=tk.LEFT, padx=(0, 5))
        
        # 搜索框
        search_frame = ttk.Frame(toolbar)
        search_frame.pack(side=tk.RIGHT)
        
        ttk.Label(search_frame, text="搜索:").pack(side=tk.LEFT)
        self.search_var = tk.StringVar()
        self.search_var.trace('w', self.on_search_change)
        search_entry = ttk.Entry(search_frame, textvariable=self.search_var, width=20)
        search_entry.pack(side=tk.LEFT, padx=(5, 0))
        
        # 创建分割窗口
        paned_window = ttk.PanedWindow(general_frame, orient=tk.HORIZONTAL)
        paned_window.pack(fill=tk.BOTH, expand=True)
        
        # 左侧：窗口列表
        left_frame = ttk.Frame(paned_window)
        paned_window.add(left_frame, weight=1)
        
        ttk.Label(left_frame, text="窗口列表", font=('Arial', 12, 'bold')).pack(pady=(0, 5))
        
        # 窗口列表树
        self.window_tree = ttk.Treeview(left_frame, columns=('class', 'pid', 'process', 'exe_path'), 
                                      show='tree headings')
        self.window_tree.heading('#0', text='窗口标题', command=lambda: self.sort_treeview('#0'))
        self.window_tree.heading('class', text='类名', command=lambda: self.sort_treeview('class'))
        self.window_tree.heading('pid', text='进程ID', command=lambda: self.sort_treeview('pid'))
        self.window_tree.heading('process', text='进程名', command=lambda: self.sort_treeview('process'))
        self.window_tree.heading('exe_path', text='EXE路径', command=lambda: self.sort_treeview('exe_path'))
        
        self.window_tree.column('#0', width=300)
        self.window_tree.column('class', width=150)
        self.window_tree.column('pid', width=80)
        self.window_tree.column('process', width=150)
        self.window_tree.column('exe_path', width=300)
        
        # 滚动条
        window_scrollbar = ttk.Scrollbar(left_frame, orient=tk.VERTICAL, 
                                       command=self.window_tree.yview)
        self.window_tree.configure(yscrollcommand=window_scrollbar.set)
        
        self.window_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        window_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        
        # 绑定选择事件
        self.window_tree.bind('<<TreeviewSelect>>', self.on_window_select)
        
        # 绑定右击菜单
        self.window_tree.bind('<Button-3>', self.on_window_right_click)
        
        # 右侧：控件列表
        right_frame = ttk.Frame(paned_window)
        paned_window.add(right_frame, weight=1)
        
        ttk.Label(right_frame, text="控件列表", font=('Arial', 12, 'bold')).pack(pady=(0, 5))
        
        # 控件列表树
        self.control_tree = ttk.Treeview(right_frame, columns=('class', 'id', 'rect'), 
                                       show='tree headings')
        self.control_tree.heading('#0', text='控件文本')
        self.control_tree.heading('class', text='类名')
        self.control_tree.heading('id', text='控件ID')
        self.control_tree.heading('rect', text='位置')
        
        self.control_tree.column('#0', width=200)
        self.control_tree.column('class', width=120)
        self.control_tree.column('id', width=80)
        self.control_tree.column('rect', width=150)
        
        # 滚动条
        control_scrollbar = ttk.Scrollbar(right_frame, orient=tk.VERTICAL, 
                                        command=self.control_tree.yview)
        self.control_tree.configure(yscrollcommand=control_scrollbar.set)
        
        self.control_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        control_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        
        # 底部：详细信息
        info_frame = ttk.LabelFrame(general_frame, text="详细信息")
        info_frame.pack(fill=tk.X, pady=(10, 0))
        
        self.info_text = scrolledtext.ScrolledText(info_frame, height=8, wrap=tk.WORD)
        self.info_text.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
    
    def setup_task_tab(self):
        """设置任务管理页签"""
        task_frame = ttk.Frame(self.notebook)
        self.notebook.add(task_frame, text="任务")
        # 使用说明
        ttk.Label(task_frame, text="使用说明：本页为定时任务功能，会按照设定的时间间隔，自动检测并结束下方列表中的进程（包括按进程名和进程ID添加的项）。请谨慎添加需要自动结束的进程。",
                  wraplength=1000, foreground="#666").pack(fill=tk.X, padx=10, pady=(10, 0))
        
        # 任务管理区域
        task_control_frame = ttk.LabelFrame(task_frame, text="任务控制")
        task_control_frame.pack(fill=tk.X, padx=10, pady=10)
        
        # 时间间隔设置
        interval_frame = ttk.Frame(task_control_frame)
        interval_frame.pack(fill=tk.X, pady=5)
        
        ttk.Label(interval_frame, text="检查间隔(秒):").pack(side=tk.LEFT)
        self.interval_var = tk.StringVar(value="60")
        interval_entry = ttk.Entry(interval_frame, textvariable=self.interval_var, width=10)
        interval_entry.pack(side=tk.LEFT, padx=(5, 10))
        
        # 任务控制按钮
        self.task_start_btn = ttk.Button(interval_frame, text="开始任务", 
                                        command=self.start_task_monitoring)
        self.task_start_btn.pack(side=tk.LEFT, padx=(0, 5))
        
        self.task_stop_btn = ttk.Button(interval_frame, text="停止任务", 
                                       command=self.stop_task_monitoring, state=tk.DISABLED)
        self.task_stop_btn.pack(side=tk.LEFT)
        
        # 进程管理区域
        process_frame = ttk.LabelFrame(task_frame, text="进程管理")
        process_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
        
        # 添加进程区域
        add_frame = ttk.Frame(process_frame)
        add_frame.pack(fill=tk.X, pady=5)
        
        # 进程名输入
        name_frame = ttk.Frame(add_frame)
        name_frame.pack(fill=tk.X, pady=2)
        
        ttk.Label(name_frame, text="进程名:").pack(side=tk.LEFT)
        self.process_name_var = tk.StringVar()
        process_entry = ttk.Entry(name_frame, textvariable=self.process_name_var, width=20)
        process_entry.pack(side=tk.LEFT, padx=(5, 10))
        
        add_name_btn = ttk.Button(name_frame, text="添加进程名", command=self.add_process_by_name)
        add_name_btn.pack(side=tk.LEFT, padx=(0, 5))
        
        # 进程ID输入
        pid_frame = ttk.Frame(add_frame)
        pid_frame.pack(fill=tk.X, pady=2)
        
        ttk.Label(pid_frame, text="进程ID:").pack(side=tk.LEFT)
        self.process_id_var = tk.StringVar()
        pid_entry = ttk.Entry(pid_frame, textvariable=self.process_id_var, width=20)
        pid_entry.pack(side=tk.LEFT, padx=(5, 10))
        
        add_pid_btn = ttk.Button(pid_frame, text="添加进程ID", command=self.add_process_by_id)
        add_pid_btn.pack(side=tk.LEFT, padx=(0, 5))
        
        # 进程列表
        list_frame = ttk.Frame(process_frame)
        list_frame.pack(fill=tk.BOTH, expand=True, pady=5)
        
        ttk.Label(list_frame, text="监控的进程列表:").pack(anchor=tk.W)
        
        # 进程列表树
        self.task_tree = ttk.Treeview(list_frame, columns=('type', 'status', 'last_check'), 
                                    show='tree headings')
        self.task_tree.heading('#0', text='进程名/ID')
        self.task_tree.heading('type', text='类型')
        self.task_tree.heading('status', text='状态')
        self.task_tree.heading('last_check', text='最后检查')
        
        self.task_tree.column('#0', width=200)
        self.task_tree.column('type', width=80)
        self.task_tree.column('status', width=100)
        self.task_tree.column('last_check', width=150)
        
        # 滚动条
        task_scrollbar = ttk.Scrollbar(list_frame, orient=tk.VERTICAL, 
                                     command=self.task_tree.yview)
        self.task_tree.configure(yscrollcommand=task_scrollbar.set)
        
        self.task_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        task_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        
        # 配置标签样式
        self.task_tree.tag_configure('pid_item', foreground='blue')
        
        # 按钮区域
        button_frame = ttk.Frame(list_frame)
        button_frame.pack(pady=(5, 0))
        
        delete_btn = ttk.Button(button_frame, text="删除选中", command=self.delete_process)
        delete_btn.pack(side=tk.LEFT, padx=(0, 5))
        
        terminate_btn = ttk.Button(button_frame, text="立即结束选中进程", command=self.terminate_selected_process)
        terminate_btn.pack(side=tk.LEFT, padx=(0, 5))
        
        terminate_all_btn = ttk.Button(button_frame, text="结束所有进程", command=self.terminate_all_processes)
        terminate_all_btn.pack(side=tk.LEFT)
        
        # 任务日志区域
        log_frame = ttk.LabelFrame(task_frame, text="任务日志")
        log_frame.pack(fill=tk.X, padx=10, pady=10)
        
        self.task_log = scrolledtext.ScrolledText(log_frame, height=6, wrap=tk.WORD)
        self.task_log.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)

    def setup_screenshot_tab(self):
        """设置截图页签"""
        shot_frame = ttk.Frame(self.notebook)
        self.notebook.add(shot_frame, text="截图")

        info_bar = ttk.Frame(shot_frame)
        info_bar.pack(fill=tk.X, padx=10, pady=(10, 5))
        # 动态显示快捷键提示（根据配置）
        self.screenshot_info_label = ttk.Label(info_bar, text="")
        self.screenshot_info_label.pack(side=tk.LEFT)
        self._update_screenshot_info_text()  # 初始化提示文字
        start_btn = ttk.Button(info_bar, text="开始截图", command=self.open_selection_overlay)
        start_btn.pack(side=tk.RIGHT)

        thumb_frame = ttk.LabelFrame(shot_frame, text="最近一次截图")
        thumb_frame.pack(fill=tk.X, padx=10, pady=10)

        self.screenshot_thumb_label = ttk.Label(thumb_frame)
        self.screenshot_thumb_label.pack(padx=10, pady=(10, 5))

        btn_bar = ttk.Frame(thumb_frame)
        btn_bar.pack(anchor=tk.W, padx=10, pady=(0, 10))
        save_btn = ttk.Button(btn_bar, text='保存到文件', command=self.save_screenshot_to_file)
        save_btn.pack(side=tk.LEFT)
        annotate_btn = ttk.Button(btn_bar, text='标注', command=self._open_annotate_window)
        annotate_btn.pack(side=tk.LEFT, padx=6)

        # 历史缩略图区域
        history_frame = ttk.LabelFrame(shot_frame, text="历史缩略图（最多9张，点击可复制到剪贴板）")
        history_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=(0, 10))
        self._history_frame = history_frame
        self._shot_history = []      # List[PIL.Image]
        self._shot_thumbs = []       # List[ImageTk.PhotoImage]
        self._thumb_labels = []      # List[ttk.Label]
        grid = ttk.Frame(history_frame)
        grid.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
        self._thumb_grid = grid
        # 预创建3x3占位
        for r in range(3):
            for c in range(3):
                idx = r * 3 + c
                lbl = ttk.Label(grid, text=f"{idx+1}", relief=tk.SOLID, width=24)
                lbl.grid(row=r, column=c, padx=5, pady=5, sticky='nsew')
                grid.grid_columnconfigure(c, weight=1)
                self._thumb_labels.append(lbl)
                lbl.bind('<Button-1>', lambda e, i=idx: self._on_thumb_click(i))
                lbl.bind('<Button-3>', lambda e, i=idx: self._on_thumb_menu(e, i))

        # 截图相关状态
        self._hotkey_registered = False
        self._overlay = None
        self._overlay_canvas = None
        self._start_xy = None
        self._rect_id = None
        self._selection_bbox = None
        self._thumb_image = None  # Tk PhotoImage 引用
        self._last_screenshot = None  # 最近一次截图（PIL Image）
        self._sel_confirm_armed = False  # 选区确认待触发状态（蓝色）
        self._crosshair_h = None
        self._crosshair_v = None
        self._size_label_id = None
        self._magnifier = None
        self._magnifier_canvas = None
        self._magnifier_img = None
        self._magnifier_size = 140
        self._magnifier_zoom = 3
        self._resize_handles = []  # canvas ids for handles
        self._active_handle = None
        self._mask_polygons = []  # 遮罩多边形ID列表
        self._mask_image_id = None
        self._mask_image_tk = None
        # 拾取节流（鼠标移动检查间隔）
        self._last_pick_ts = 0.0
        self._last_mouse_xy = None
        self._auto_window_detection = True  # 自动窗口检测
        self._last_detected_window = None  # 上次检测到的窗口矩形
        self._is_dragging = False  # 标记是否正在拖动
        self._click_point = None  # 记录鼠标按下位置
        self._was_window_selected = False  # 标记是否已通过点击选择了窗口
        self._selection_locked = False  # 标记选区是否已锁定（手动选择完成后不再自动匹配窗口）
        self._dragging_edge = None  # 标记正在拖动的边框（'top', 'bottom', 'left', 'right'）
        self._resize_anchor_bbox = None  # 记录按下句柄时的原始bbox，作为锚点
    
    def setup_guardian_tab(self):
        """设置守护页签"""
        guardian_frame = ttk.Frame(self.notebook)
        self.notebook.add(guardian_frame, text="守护")
        
        # 顶部工具栏
        toolbar = ttk.Frame(guardian_frame)
        toolbar.pack(fill=tk.X, padx=10, pady=10)
        
        ttk.Label(toolbar, text="守护进程管理 - 自动启动指定程序").pack(side=tk.LEFT)
        
        # 进程列表
        list_frame = ttk.LabelFrame(guardian_frame, text="守护进程列表")
        list_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=(0, 10))
        
        # 创建Treeview
        tree_scroll = ttk.Scrollbar(list_frame)
        tree_scroll.pack(side=tk.RIGHT, fill=tk.Y)
        
        self.guardian_tree = ttk.Treeview(list_frame, columns=('路径', '参数', '间隔'), 
                                          show='headings', yscrollcommand=tree_scroll.set)
        self.guardian_tree.heading('路径', text='程序路径')
        self.guardian_tree.heading('参数', text='命令行参数')
        self.guardian_tree.heading('间隔', text='检查间隔(秒)')
        self.guardian_tree.column('路径', width=300)
        self.guardian_tree.column('参数', width=200)
        self.guardian_tree.column('间隔', width=100)
        self.guardian_tree.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
        tree_scroll.config(command=self.guardian_tree.yview)
        
        # 按钮区域
        btn_frame = ttk.Frame(guardian_frame)
        btn_frame.pack(fill=tk.X, padx=10, pady=(0, 10))
        
        ttk.Button(btn_frame, text="添加进程", command=self.add_guardian_process).pack(side=tk.LEFT, padx=5)
        ttk.Button(btn_frame, text="编辑", command=self.edit_guardian_process).pack(side=tk.LEFT, padx=5)
        ttk.Button(btn_frame, text="删除", command=self.delete_guardian_process).pack(side=tk.LEFT, padx=5)
        
        # 控制区域
        control_frame = ttk.LabelFrame(guardian_frame, text="守护控制")
        control_frame.pack(fill=tk.X, padx=10, pady=(0, 10))
        
        control_inner = ttk.Frame(control_frame)
        control_inner.pack(fill=tk.X, padx=10, pady=10)
        
        ttk.Label(control_inner, text="全局检查间隔(秒):").pack(side=tk.LEFT, padx=5)
        self.guardian_interval_var = tk.IntVar(value=60)
        ttk.Spinbox(control_inner, from_=10, to=3600, textvariable=self.guardian_interval_var, width=10).pack(side=tk.LEFT, padx=5)
        
        self.guardian_start_btn = ttk.Button(control_inner, text="开始守护", command=self.start_guardian)
        self.guardian_start_btn.pack(side=tk.LEFT, padx=20)
        
        self.guardian_stop_btn = ttk.Button(control_inner, text="停止守护", command=self.stop_guardian, state=tk.DISABLED)
        self.guardian_stop_btn.pack(side=tk.LEFT, padx=5)
        
        # 日志区域
        log_frame = ttk.LabelFrame(guardian_frame, text="守护日志")
        log_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=(0, 10))
        
        self.guardian_log = scrolledtext.ScrolledText(log_frame, height=6, wrap=tk.WORD)
        self.guardian_log.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
        
        # 守护状态变量
        self.guardian_running = False
        self.guardian_thread = None
        self.guardian_processes = []  # 守护进程列表
    
    def setup_port_tab(self):
        """设置端口vs进程页签"""
        port_frame = ttk.Frame(self.notebook)
        self.notebook.add(port_frame, text="端口vs进程")
        
        # 顶部搜索栏
        search_frame = ttk.Frame(port_frame)
        search_frame.pack(fill=tk.X, padx=10, pady=10)
        
        # 端口查询
        ttk.Label(search_frame, text="端口号:").pack(side=tk.LEFT, padx=5)
        self.port_entry = ttk.Entry(search_frame, width=15)
        self.port_entry.pack(side=tk.LEFT, padx=5)
        ttk.Button(search_frame, text="查询端口", command=self.search_port).pack(side=tk.LEFT, padx=5)
        
        # 进程名查询
        ttk.Label(search_frame, text="进程名:").pack(side=tk.LEFT, padx=(20, 5))
        self.process_name_entry = ttk.Entry(search_frame, width=15)
        self.process_name_entry.pack(side=tk.LEFT, padx=5)
        ttk.Button(search_frame, text="查询进程", command=self.search_process_ports).pack(side=tk.LEFT, padx=5)
        
        # 其他按钮
        ttk.Button(search_frame, text="查询所有", command=self.search_all_ports).pack(side=tk.LEFT, padx=(20, 5))
        ttk.Button(search_frame, text="清空", command=self.clear_port_results).pack(side=tk.LEFT, padx=5)
        
        # 统计信息
        self.port_stats_label = ttk.Label(search_frame, text="")
        self.port_stats_label.pack(side=tk.LEFT, padx=20)
        
        # 结果列表
        result_frame = ttk.LabelFrame(port_frame, text="端口使用情况")
        result_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=(0, 10))
        
        # 创建Treeview
        tree_scroll_y = ttk.Scrollbar(result_frame)
        tree_scroll_y.pack(side=tk.RIGHT, fill=tk.Y)
        
        tree_scroll_x = ttk.Scrollbar(result_frame, orient=tk.HORIZONTAL)
        tree_scroll_x.pack(side=tk.BOTTOM, fill=tk.X)
        
        self.port_tree = ttk.Treeview(result_frame, 
                                       columns=('进程名', 'PID', '协议', '本地地址', '远程地址', '状态'),
                                       show='headings',
                                       yscrollcommand=tree_scroll_y.set,
                                       xscrollcommand=tree_scroll_x.set)
        
        self.port_tree.heading('进程名', text='进程名')
        self.port_tree.heading('PID', text='PID')
        self.port_tree.heading('协议', text='协议')
        self.port_tree.heading('本地地址', text='本地地址')
        self.port_tree.heading('远程地址', text='远程地址')
        self.port_tree.heading('状态', text='状态')
        
        self.port_tree.column('进程名', width=150)
        self.port_tree.column('PID', width=80)
        self.port_tree.column('协议', width=60)
        self.port_tree.column('本地地址', width=150)
        self.port_tree.column('远程地址', width=150)
        self.port_tree.column('状态', width=120)
        
        self.port_tree.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
        tree_scroll_y.config(command=self.port_tree.yview)
        tree_scroll_x.config(command=self.port_tree.xview)
        
        # 右键菜单
        self.port_context_menu = tk.Menu(self.port_tree, tearoff=0)
        self.port_context_menu.add_command(label="复制进程名", command=lambda: self.copy_port_info('进程名'))
        self.port_context_menu.add_command(label="复制PID", command=lambda: self.copy_port_info('PID'))
        self.port_context_menu.add_command(label="复制本地地址", command=lambda: self.copy_port_info('本地地址'))
        self.port_context_menu.add_separator()
        self.port_context_menu.add_command(label="结束进程", command=self.kill_port_process)
        
        self.port_tree.bind('<Button-3>', self.show_port_context_menu)
        
        # 说明文字
        info_frame = ttk.Frame(port_frame)
        info_frame.pack(fill=tk.X, padx=10, pady=(0, 10))
        
        info_text = "状态说明: LISTEN(监听), ESTABLISHED(已连接), TIME_WAIT(等待), CLOSE_WAIT(关闭等待)"
        ttk.Label(info_frame, text=info_text, foreground='gray').pack(side=tk.LEFT)
    
    def load_config(self):
        """加载配置文件"""
        try:
            # 从配置管理器加载进程列表
            processes = self.config.get_processes()
            for process_item in processes:
                process_name = process_item.get('name', '')
                process_type = process_item.get('type', 'name')
                type_display = '进程ID' if process_type == 'pid' else '进程名'
                
                tags = ('pid_item',) if process_type == 'pid' else ()
                self.task_tree.insert('', 'end', text=process_name, 
                                    values=(type_display, '等待', '未检查'), tags=tags)
                self.task_processes.append(process_item)
            
            # 加载检查间隔
            self.task_interval = self.config.get_interval()
            self.interval_var.set(str(self.task_interval))
            
            # 加载快捷键设置并应用
            self._apply_hotkeys_from_config()
            
            # 加载守护进程列表
            guardian_processes = self.config.get_guardian_processes()
            for gp in guardian_processes:
                path = gp.get('path', '')
                args = gp.get('args', '')
                interval = gp.get('interval', 60)
                self.guardian_tree.insert('', 'end', values=(path, args, interval))
                self.guardian_processes.append(gp)
            
            # 加载守护检查间隔
            guardian_interval = self.config.get_guardian_interval()
            self.guardian_interval_var.set(guardian_interval)
            
            self.log_task(f"已加载配置: {len(self.task_processes)} 个进程, {len(self.guardian_processes)} 个守护进程")
        except Exception as e:
            self.log_task(f"加载配置失败: {e}")
    
    def save_config(self):
        """保存配置文件"""
        try:
            # 保存进程列表
            self.config.set_processes(self.task_processes)
            
            # 保存检查间隔
            try:
                interval = int(self.interval_var.get())
                if interval > 0:
                    self.config.set_interval(interval)
                    self.task_interval = interval
            except ValueError:
                pass
            
            # 保存守护进程列表
            self.config.set_guardian_processes(self.guardian_processes)
            
            # 保存守护检查间隔
            try:
                guardian_interval = int(self.guardian_interval_var.get())
                if guardian_interval > 0:
                    self.config.set_guardian_interval(guardian_interval)
            except ValueError:
                pass
            
            # 快捷键设置通过属性窗口保存，这里不需要保存
            
            # 保存配置到文件
            self.config.save()
            
            self.log_task(f"已保存配置: {len(self.task_processes)} 个进程")
        except Exception as e:
            self.log_task(f"保存配置失败: {e}")
    
    def sort_treeview(self, column):
        """对TreeView进行排序"""
        # 如果点击的是当前排序列，则切换排序方向
        if self.sort_column == column:
            self.sort_reverse = not self.sort_reverse
        else:
            self.sort_column = column
            self.sort_reverse = False
        
        # 获取所有项目
        items = []
        for item in self.window_tree.get_children():
            items.append(item)
        
        # 根据列进行排序
        def get_sort_key(item):
            if column == '#0':
                # 窗口标题
                return self.window_tree.item(item, 'text').lower()
            else:
                # 其他列
                values = self.window_tree.item(item, 'values')
                if values and len(values) > 0:
                    if column == 'pid':
                        # 进程ID按数字排序
                        try:
                            return int(values[1]) if values[1] else 0
                        except (ValueError, IndexError):
                            return 0
                    elif column == 'class':
                        return values[0].lower() if values[0] else ''
                    elif column == 'process':
                        return values[2].lower() if len(values) > 2 and values[2] else ''
                    elif column == 'exe_path':
                        return values[3].lower() if len(values) > 3 and values[3] else ''
                return ''
        
        # 排序
        items.sort(key=get_sort_key, reverse=self.sort_reverse)
        
        # 重新排列项目
        for i, item in enumerate(items):
            self.window_tree.move(item, '', i)
        
        # 更新列头显示排序方向
        self.update_column_headers()
    
    def update_column_headers(self):
        """更新列头显示排序方向"""
        columns = ['#0', 'class', 'pid', 'process', 'exe_path']
        column_names = ['窗口标题', '类名', '进程ID', '进程名', 'EXE路径']
        
        for i, col in enumerate(columns):
            name = column_names[i]
            if col == self.sort_column:
                if self.sort_reverse:
                    name += ' ↓'
                else:
                    name += ' ↑'
            self.window_tree.heading(col, text=name)

    def _register_hotkey(self):
        """注册全局热键（从配置加载）"""
        if not self._hotkey_registered:
            self._apply_hotkeys_from_config()
            self._hotkey_registered = True

    def _update_screenshot_info_text(self):
        """更新截图提示文字"""
        try:
            hotkey = self.config.get_hotkey_start()
            # 格式化快捷键显示（首字母大写）
            hotkey_display = '+'.join(word.capitalize() for word in hotkey.split('+'))
            text = f"按 {hotkey_display} 或点击右侧按钮开始截图；Enter 确认，Esc 取消。"
            if hasattr(self, 'screenshot_info_label'):
                self.screenshot_info_label.config(text=text)
        except Exception as e:
            debug_log(f"[HOTKEY] Error updating screenshot info text: {e}")
    
    def _apply_hotkeys_from_config(self):
        """从配置加载并应用快捷键"""
        try:
            import keyboard
            # 移除旧的快捷键
            try:
                keyboard.remove_all_hotkeys()
            except Exception:
                pass
            
            # 应用开始截图快捷键
            hk_start = self.config.get_hotkey_start() or 'ctrl+alt+i'
            def safe_open_screenshot():
                try:
                    self.root.after(0, self.open_selection_overlay)
                except Exception as e:
                    debug_log(f"[HOTKEY] Error calling open_selection_overlay: {e}")
            
            try:
                keyboard.add_hotkey(hk_start, safe_open_screenshot)
                debug_log(f"[HOTKEY] Registered start hotkey: {hk_start}")
            except Exception as e:
                debug_log(f"[HOTKEY] Failed to register start hotkey {hk_start}: {e}")
            
            # 应用全屏截图快捷键
            hk_full = self.config.get_hotkey_full() or 'ctrl+alt+shift+i'
            def safe_full_screenshot():
                try:
                    self.root.after(0, self._quick_full_screenshot)
                except Exception as e:
                    debug_log(f"[HOTKEY] Error calling _quick_full_screenshot: {e}")
            
            try:
                keyboard.add_hotkey(hk_full, safe_full_screenshot)
                debug_log(f"[HOTKEY] Registered full screenshot hotkey: {hk_full}")
            except Exception as e:
                debug_log(f"[HOTKEY] Failed to register full hotkey {hk_full}: {e}")
            
            # 更新提示文字
            self._update_screenshot_info_text()
            
        except Exception as e:
            debug_log(f"[HOTKEY] Failed to apply hotkeys from config: {e}")

    def _quick_full_screenshot(self):
        try:
            from PIL import ImageGrab, ImageTk
            img = ImageGrab.grab()
            self._last_screenshot = img.copy()
            thumb = img.copy(); thumb.thumbnail((600, 400))
            self._thumb_image = ImageTk.PhotoImage(thumb)
            self.screenshot_thumb_label.configure(image=self._thumb_image)
            self._copy_image_to_clipboard(img)
            # 入历史
            if self._shot_history is None:
                self._shot_history = []
            self._shot_history.insert(0, img.copy())
            self._shot_history = self._shot_history[:9]
            self._update_thumbnail_history()
        except Exception as e:
            try:
                self.log_task(f"全屏截图失败: {e}")
            except Exception:
                pass

    def _hide_to_tray(self):
        """隐藏窗口到托盘"""
        debug_log("[TRAY] _hide_to_tray called")
        try:
            if self.tray_manager:
                debug_log("[TRAY] tray_manager exists, calling hide_to_tray")
                self.tray_manager.hide_to_tray()
                debug_log("[TRAY] hide_to_tray completed")
            else:
                debug_log("[TRAY] WARNING: tray_manager is None!")
        except Exception as e:
            debug_log(f"[TRAY] ERROR in _hide_to_tray: {e}")
    
    def _show_from_tray(self):
        """从托盘显示窗口"""
        debug_log("[TRAY] _show_from_tray called")
        try:
            if self.tray_manager:
                debug_log("[TRAY] tray_manager exists, calling show_from_tray")
                self.tray_manager.show_from_tray()
                debug_log("[TRAY] show_from_tray completed")
            else:
                debug_log("[TRAY] WARNING: tray_manager is None!")
        except Exception as e:
            debug_log(f"[TRAY] ERROR in _show_from_tray: {e}")
    
    def _open_settings(self):
        """打开设置对话框"""
        # 先显示主窗口
        self._show_from_tray()
        
        # 创建设置对话框
        settings_window = tk.Toplevel(self.root)
        settings_window.title("属性设置")
        settings_window.geometry("500x400")
        settings_window.transient(self.root)
        settings_window.grab_set()
        
        # 创建主框架
        main_frame = ttk.Frame(settings_window, padding="20")
        main_frame.pack(fill=tk.BOTH, expand=True)
        
        # 标题
        title_label = ttk.Label(main_frame, text="程序设置", font=("", 14, "bold"))
        title_label.pack(pady=(0, 20))
        
        # 截图设置
        screenshot_frame = ttk.LabelFrame(main_frame, text="截图设置", padding="10")
        screenshot_frame.pack(fill=tk.X, pady=(0, 15))
        
        hide_var = tk.BooleanVar(value=self.config.get_screenshot_hide_main_window())
        hide_check = ttk.Checkbutton(
            screenshot_frame,
            text="截图时隐藏主窗口到托盘",
            variable=hide_var
        )
        hide_check.pack(anchor=tk.W)
        
        # 快捷键设置
        hotkey_frame = ttk.LabelFrame(main_frame, text="快捷键设置", padding="10")
        hotkey_frame.pack(fill=tk.X, pady=(0, 15))
        
        ttk.Label(hotkey_frame, text="开始截图:").grid(row=0, column=0, sticky=tk.W, pady=5)
        hotkey_start_var = tk.StringVar(value=self.config.get_hotkey_start())
        hotkey_start_entry = ttk.Entry(hotkey_frame, textvariable=hotkey_start_var, width=30)
        hotkey_start_entry.grid(row=0, column=1, sticky=tk.W, padx=10, pady=5)
        
        ttk.Label(hotkey_frame, text="全屏截图:").grid(row=1, column=0, sticky=tk.W, pady=5)
        hotkey_full_var = tk.StringVar(value=self.config.get_hotkey_full())
        hotkey_full_entry = ttk.Entry(hotkey_frame, textvariable=hotkey_full_var, width=30)
        hotkey_full_entry.grid(row=1, column=1, sticky=tk.W, padx=10, pady=5)
        
        # 任务监控设置
        task_frame = ttk.LabelFrame(main_frame, text="任务监控设置", padding="10")
        task_frame.pack(fill=tk.X, pady=(0, 15))
        
        ttk.Label(task_frame, text="检查间隔(秒):").grid(row=0, column=0, sticky=tk.W, pady=5)
        interval_var = tk.IntVar(value=self.config.get_interval())
        interval_spinbox = ttk.Spinbox(task_frame, from_=10, to=3600, textvariable=interval_var, width=28)
        interval_spinbox.grid(row=0, column=1, sticky=tk.W, padx=10, pady=5)
        
        # 按钮区域
        button_frame = ttk.Frame(main_frame)
        button_frame.pack(fill=tk.X, pady=(20, 0))
        
        def save_settings():
            # 保存设置
            self.config.set_screenshot_hide_main_window(hide_var.get())
            self.config.set_hotkey_start(hotkey_start_var.get())
            self.config.set_hotkey_full(hotkey_full_var.get())
            self.config.set_interval(interval_var.get())
            self.config.save()
            
            # 更新任务间隔
            self.task_interval = interval_var.get()
            
            messagebox.showinfo("成功", "设置已保存", parent=settings_window)
            settings_window.destroy()
        
        def cancel_settings():
            settings_window.destroy()
        
        ttk.Button(button_frame, text="保存", command=save_settings).pack(side=tk.RIGHT, padx=5)
        ttk.Button(button_frame, text="取消", command=cancel_settings).pack(side=tk.RIGHT)
    
    def open_selection_overlay(self):
        """打开全屏选区覆盖层"""
        debug_log("[SCREENSHOT] open_selection_overlay: entered")
        if self._overlay is not None:
            debug_log("[SCREENSHOT] overlay already exists, return")
            return
        
        # 根据配置隐藏主窗口到托盘
        hide_enabled = self.config.get_screenshot_hide_main_window()
        debug_log(f"[SCREENSHOT] hide_main_window config: {hide_enabled}")
        if hide_enabled:
            debug_log("[SCREENSHOT] Attempting to hide window to tray...")
            self._hide_to_tray()
            debug_log("[SCREENSHOT] Window hide completed")
            # 等待窗口完全隐藏后再抓取屏幕和创建覆盖层
            # 延迟200ms确保窗口完全隐藏
            self.root.after(200, self._capture_and_create_overlay)
            return
        else:
            debug_log("[SCREENSHOT] hide_main_window is disabled, skipping hide")
        
        # 如果不隐藏窗口，直接抓取并创建覆盖层
        self._capture_and_create_overlay()
    
    def _capture_and_create_overlay(self):
        """抓取屏幕并创建覆盖层"""
        # 抓取整屏图像作为背景
        try:
            from PIL import ImageGrab, ImageTk
            self._screen_image = ImageGrab.grab()
            self._screen_tkimage = ImageTk.PhotoImage(self._screen_image)
        except Exception as e:
            try:
                self.log_task(f"获取屏幕图像失败: {e}")
            except Exception:
                pass
            return
        
        # 创建覆盖层
        self._create_screenshot_overlay()
    
    def _create_screenshot_overlay(self):
        """创建截图覆盖层（内部方法）"""
        if self._overlay is not None:
            return

        self._overlay = tk.Toplevel(self.root)
        self._overlay.attributes('-fullscreen', True)
        self._overlay.attributes('-topmost', True)
        self._overlay.overrideredirect(True)

        # 初始化相关变量
        self._start_xy = None
        self._selection_bbox = None
        self._rect_id = None
        self._sel_confirm_armed = False
        self._mask_polygons = []
        self._last_detected_window = None
        self._is_dragging = False
        self._click_point = None
        self._was_window_selected = False
        self._selection_locked = False
        self._dragging_edge = None
        self._active_handle = None
        self._resize_anchor_bbox = None
        # 重置拾取节流状态
        self._last_pick_ts = 0.0
        self._last_mouse_xy = None
        self._initial_mouse_pos = None  # 记录初始鼠标位置，用于10像素移动检测
        self._auto_window_detection = False  # 初始为False，移动10像素后才开启

        # 画布显示屏幕图像，并用于绘制选区
        self._overlay_canvas = tk.Canvas(self._overlay, bg='black', highlightthickness=0, cursor='cross')
        self._overlay_canvas.pack(fill=tk.BOTH, expand=True)
        # 绘制背景屏幕图
        self._overlay_canvas.create_image(0, 0, anchor='nw', image=self._screen_tkimage)
        
        # 立即显示半透明遮罩（全屏50%透明）
        self._crosshair_h = None
        self._crosshair_v = None
        self._magnifier = None
        self._magnifier_canvas = None
        self._magnifier_img = None
        self._magnifier_size = 150
        self._magnifier_zoom = 3
        self._update_mask()  # 立即显示遮罩
        
        # 获取初始鼠标位置并立即显示放大镜
        try:
            import win32api
            mx, my = win32api.GetCursorPos()
            # 转换为画布坐标
            canvas_x = mx - self._overlay.winfo_rootx()
            canvas_y = my - self._overlay.winfo_rooty()
            self._initial_mouse_pos = (mx, my)  # 保存屏幕坐标
            self._update_magnifier(canvas_x, canvas_y)  # 立即显示放大镜
            # 立即显示十字线
            w = self._overlay.winfo_width()
            h = self._overlay.winfo_height()
            if canvas_x >= 0 and canvas_x < w and canvas_y >= 0 and canvas_y < h:
                self._crosshair_h = self._overlay_canvas.create_line(0, canvas_y, w, canvas_y, fill='#00aaff', dash=(2, 4))
                self._crosshair_v = self._overlay_canvas.create_line(canvas_x, 0, canvas_x, h, fill='#00aaff', dash=(2, 4))
        except Exception as e:
            debug_log(f"[SCREENSHOT] Error getting initial mouse position: {e}")

        # 底部按钮区
        btn_frame = tk.Frame(self._overlay)
        btn_frame.place(relx=0.5, rely=0.98, anchor='s')
        confirm_btn = ttk.Button(btn_frame, text='确认', command=self._confirm_selection)
        cancel_btn = ttk.Button(btn_frame, text='取消', command=self._cancel_selection)
        confirm_btn.pack(side=tk.LEFT, padx=5)
        cancel_btn.pack(side=tk.LEFT, padx=5)

        # 绑定鼠标事件
        self._overlay_canvas.bind('<ButtonPress-1>', self._on_sel_press)
        self._overlay_canvas.bind('<B1-Motion>', self._on_sel_drag)
        self._overlay_canvas.bind('<ButtonRelease-1>', self._on_sel_release)
        # 双击确认：在已有选区内双击等同确认；无选区则全屏
        self._overlay_canvas.bind('<Double-1>', self._on_double_click)
        # 右击取消"待确认"状态
        self._overlay_canvas.bind('<Button-3>', self._on_right_click)
        # 移动时更新十字线、放大镜
        self._overlay_canvas.bind('<Motion>', self._on_mouse_move)

        # 绑定键盘事件（Enter确认；ESC取消）
        self._overlay.bind('<Return>', lambda e: self._confirm_selection())
        self._overlay.bind('<Escape>', lambda e: self._cancel_selection())
        try:
            self._overlay.focus_force()
        except Exception:
            pass

    def _on_sel_press(self, event):
        """鼠标左键按下事件"""
        x, y = event.x, event.y
        self._click_point = (x, y)
        self._is_dragging = False
        self._was_window_selected = False
        
        # 如果已经有选区框
        if self._selection_bbox:
            # 首先检查是否点击在句柄上
            handle = self._hit_test_handle(x, y)
            if handle:
                # 句柄点击，记录当前句柄与锚点bbox，允许后续拖动仅改变两条边
                debug_log(f"[句柄按下] 检测到句柄: {handle}, 位置: ({x}, {y})")
                debug_log(f"[句柄按下] 当前bbox: {self._selection_bbox}")
                self._active_handle = handle
                self._resize_anchor_bbox = self._selection_bbox
                debug_log(f"[句柄按下] 设置_active_handle={handle}, _resize_anchor_bbox={self._resize_anchor_bbox}")
                # 记录起点，避免拖动时因_start_xy为空而被提前return
                self._start_xy = (x, y)
                # 进入句柄拖动即退出待确认状态
                self._sel_confirm_armed = False
                return
            # 检查是否点击在边框上（但不在句柄上）
            elif self._hit_test_edge(x, y):
                # 点击在边框上，标记为拖动边框
                debug_log(f"[边框按下] 命中边: {self._hit_test_edge(x, y)}, 位置: ({x}, {y}), bbox={self._selection_bbox}")
                self._dragging_edge = self._hit_test_edge(x, y)
                self._start_xy = (x, y)
                self._sel_confirm_armed = False  # 取消待确认状态（如果有）
                if self._rect_id:
                    self._overlay_canvas.itemconfig(self._rect_id, outline='red')
                # 不清除选区框，允许拖动边框
                return
            # 检查是否点击在框内
            elif self._point_in_bbox(self._selection_bbox, x, y):
                debug_log(f"[框内按下] 点在框内, 位置: ({x}, {y}), bbox={self._selection_bbox}, locked={self._selection_locked}")
                # 不在句柄上，检查两次点击确认逻辑
                # 模式2（已锁定状态）下的两次点击确认
                if self._selection_locked:
                    if not self._sel_confirm_armed:
                        # 红色编辑态：按下后准备拖动整体移动，确保维持红色态
                        self._sel_confirm_armed = False
                        if self._rect_id:
                            self._overlay_canvas.itemconfig(self._rect_id, outline='red')
                        self._start_xy = (x, y)
                        return
                    else:
                        # 蓝色待确认态：再次点击确认截图
                        self._confirm_selection()
                        return
                else:
                    # 模式1：检查是否是自动匹配的窗口
                    screen_x = self._overlay.winfo_rootx() + x
                    screen_y = self._overlay.winfo_rooty() + y
                    window_rect = self._get_window_at_point(screen_x, screen_y)
                    if window_rect:
                        # 将窗口矩形转换为画布坐标进行比较
                        wx0 = window_rect[0] - self._overlay.winfo_rootx()
                        wy0 = window_rect[1] - self._overlay.winfo_rooty()
                        wx1 = window_rect[2] - self._overlay.winfo_rootx()
                        wy1 = window_rect[3] - self._overlay.winfo_rooty()
                        # 如果当前选区框和窗口框匹配，则认为是窗口选择
                        sx0, sy0, sx1, sy1 = self._selection_bbox
                        if abs(wx0 - sx0) < 5 and abs(wy0 - sy0) < 5 and abs(wx1 - sx1) < 5 and abs(wy1 - sy1) < 5:
                            # 标记：用户点击窗口框，等待释放后确认选择
                            self._was_window_selected = True
                            self._sel_confirm_armed = False
                            return
        
        # 其他情况：点击在选区外部阴影区域时，保持原有选区，不做任何清理
        debug_log(f"[框外按下] 点在框外, 位置: ({x}, {y}), 保持原选区, locked={self._selection_locked}")
        self._sel_confirm_armed = False
        return

    def _on_sel_drag(self, event):
        """鼠标拖动事件"""
        # 标记正在拖动，阻止自动窗口匹配
        self._is_dragging = True
        
        # 如果已经通过点击选择了窗口，拖动时取消窗口选择，进入手动框选模式
        if self._was_window_selected:
            # 如果拖动距离很小，认为是点击抖动，忽略
            if self._click_point:
                dx = abs(event.x - self._click_point[0])
                dy = abs(event.y - self._click_point[1])
                if dx < 3 and dy < 3:
                    return
            # 拖动距离较大，取消窗口选择，进入手动框选模式
            self._was_window_selected = False
            if not self._start_xy:
                self._start_xy = self._click_point if self._click_point else (event.x, event.y)
            # 清除之前的选区框
            if self._rect_id:
                self._overlay_canvas.delete(self._rect_id)
                self._rect_id = None
            for pid in self._mask_polygons:
                self._overlay_canvas.delete(pid)
            self._mask_polygons = []
            self._selection_bbox = None
            # 注意：一旦开始拖动，就进入了手动模式，释放后应该锁定（不再自动匹配窗口）
        
        if not self._start_xy:
            return
            
        x1, y1 = event.x, event.y  # 画布坐标
        
        # 若拖动的是句柄，执行缩放（只改变对应的两条边，固定另外两条边）
        if self._active_handle and self._selection_bbox:
            # 以按下时的锚点bbox为基准，保证除目标两边外其它边保持稳定
            anchor = self._resize_anchor_bbox or self._selection_bbox
            left, top, right, bottom = anchor
            handle = self._active_handle
            min_size = 0

            # 记录拖动前的坐标
            debug_log(f"[句柄拖动] 拖动开始 - 句柄: {handle}, 鼠标位置: ({event.x}, {event.y})")
            debug_log(f"[句柄拖动] 拖动前bbox: left={left}, top={top}, right={right}, bottom={bottom}")
            debug_log(f"[句柄拖动] _is_dragging={self._is_dragging}, _selection_locked={self._selection_locked}")
            debug_log(f"[句柄拖动] _active_handle={self._active_handle}, _resize_anchor_bbox={self._resize_anchor_bbox}")

            # 角句柄：只更新对应的两条边，固定对角
            if handle == 'nw':
                old_left, old_top = left, top
                new_left = max(0, event.x)
                new_top = max(0, event.y)
                debug_log(f"[角句柄nw] 固定(对角): right={right}, bottom={bottom}")
                debug_log(f"[角句柄nw] 变化: left {old_left}->{new_left}, top {old_top}->{new_top}")
                self._selection_bbox = (new_left, new_top, right, bottom)
                # 交叉则翻转句柄
                if new_left > right and new_top > bottom:
                    debug_log(f"[句柄翻转] nw -> se (双向越界)")
                    self._selection_bbox = (right, bottom, new_left, new_top)
                    self._active_handle = 'se'
                    self._resize_anchor_bbox = self._selection_bbox
                elif new_left > right:
                    debug_log(f"[句柄翻转] nw -> ne (水平越界)")
                    self._selection_bbox = (right, new_top, new_left, bottom)
                    self._active_handle = 'ne'
                    self._resize_anchor_bbox = self._selection_bbox
                elif new_top > bottom:
                    debug_log(f"[句柄翻转] nw -> sw (垂直越界)")
                    self._selection_bbox = (new_left, bottom, right, new_top)
                    self._active_handle = 'sw'
                    self._resize_anchor_bbox = self._selection_bbox
            elif handle == 'ne':
                old_right, old_top = right, top
                new_right = max(0, event.x)
                new_top = max(0, event.y)
                debug_log(f"[角句柄ne] 固定(对角): left={left}, bottom={bottom}")
                debug_log(f"[角句柄ne] 变化: right {old_right}->{new_right}, top {old_top}->{new_top}")
                self._selection_bbox = (left, new_top, new_right, bottom)
                # 交叉则翻转句柄
                if new_right < left and new_top > bottom:
                    debug_log(f"[句柄翻转] ne -> sw (双向越界)")
                    self._selection_bbox = (new_right, bottom, left, new_top)
                    self._active_handle = 'sw'
                    self._resize_anchor_bbox = self._selection_bbox
                elif new_right < left:
                    debug_log(f"[句柄翻转] ne -> nw (水平越界)")
                    self._selection_bbox = (new_right, new_top, left, bottom)
                    self._active_handle = 'nw'
                    self._resize_anchor_bbox = self._selection_bbox
                elif new_top > bottom:
                    debug_log(f"[句柄翻转] ne -> se (垂直越界)")
                    self._selection_bbox = (left, bottom, new_right, new_top)
                    self._active_handle = 'se'
                    self._resize_anchor_bbox = self._selection_bbox
            elif handle == 'sw':
                old_left, old_bottom = left, bottom
                new_left = max(0, event.x)
                new_bottom = max(0, event.y)
                debug_log(f"[角句柄sw] 固定(对角): right={right}, top={top}")
                debug_log(f"[角句柄sw] 变化: left {old_left}->{new_left}, bottom {old_bottom}->{new_bottom}")
                self._selection_bbox = (new_left, top, right, new_bottom)
                # 交叉则翻转句柄
                if new_left > right and new_bottom < top:
                    debug_log(f"[句柄翻转] sw -> ne (双向越界)")
                    self._selection_bbox = (right, new_bottom, new_left, top)
                    self._active_handle = 'ne'
                    self._resize_anchor_bbox = self._selection_bbox
                elif new_left > right:
                    debug_log(f"[句柄翻转] sw -> se (水平越界)")
                    self._selection_bbox = (right, top, new_left, new_bottom)
                    self._active_handle = 'se'
                    self._resize_anchor_bbox = self._selection_bbox
                elif new_bottom < top:
                    debug_log(f"[句柄翻转] sw -> nw (垂直越界)")
                    self._selection_bbox = (new_left, new_bottom, right, top)
                    self._active_handle = 'nw'
                    self._resize_anchor_bbox = self._selection_bbox
            elif handle == 'se':
                old_right, old_bottom = right, bottom
                new_right = max(0, event.x)
                new_bottom = max(0, event.y)
                debug_log(f"[角句柄se] 固定(对角): left={left}, top={top}")
                debug_log(f"[角句柄se] 变化: right {old_right}->{new_right}, bottom {old_bottom}->{new_bottom}")
                self._selection_bbox = (left, top, new_right, new_bottom)
                # 交叉则翻转句柄
                if new_right < left and new_bottom < top:
                    debug_log(f"[句柄翻转] se -> nw (双向越界)")
                    self._selection_bbox = (new_right, new_bottom, left, top)
                    self._active_handle = 'nw'
                    self._resize_anchor_bbox = self._selection_bbox
                elif new_right < left:
                    debug_log(f"[句柄翻转] se -> sw (水平越界)")
                    self._selection_bbox = (new_right, top, left, new_bottom)
                    self._active_handle = 'sw'
                    self._resize_anchor_bbox = self._selection_bbox
                elif new_bottom < top:
                    debug_log(f"[句柄翻转] se -> ne (垂直越界)")
                    self._selection_bbox = (left, new_bottom, new_right, top)
                    self._active_handle = 'ne'
                    self._resize_anchor_bbox = self._selection_bbox
            else:
                # 边句柄：只更新一条边，固定另外三条
                if handle == 'n':
                    old_top = top
                    new_top = max(0, min(bottom - min_size, event.y))
                    debug_log(f"[边句柄n] 固定: left={left}, right={right}, bottom={bottom}")
                    debug_log(f"[边句柄n] 变化: top {old_top}->{new_top}")
                    self._selection_bbox = (left, new_top, right, bottom)
                elif handle == 's':
                    old_bottom = bottom
                    new_bottom = max(top + min_size, event.y)
                    debug_log(f"[边句柄s] 固定: left={left}, top={top}, right={right}")
                    debug_log(f"[边句柄s] 变化: bottom {old_bottom}->{new_bottom}")
                    self._selection_bbox = (left, top, right, new_bottom)
                elif handle == 'w':
                    old_left = left
                    new_left = max(0, min(right - min_size, event.x))
                    debug_log(f"[边句柄w] 固定: top={top}, right={right}, bottom={bottom}")
                    debug_log(f"[边句柄w] 变化: left {old_left}->{new_left}")
                    self._selection_bbox = (new_left, top, right, bottom)
                elif handle == 'e':
                    old_right = right
                    new_right = max(left + min_size, event.x)
                    debug_log(f"[边句柄e] 固定: left={left}, top={top}, bottom={bottom}")
                    debug_log(f"[边句柄e] 变化: right {old_right}->{new_right}")
                    self._selection_bbox = (left, top, new_right, bottom)

            # 记录拖动后的坐标（在redraw之前）
            new_left_check, new_top_check, new_right_check, new_bottom_check = self._selection_bbox
            debug_log(f"[句柄拖动] 拖动后bbox(设置后): left={new_left_check}, top={new_top_check}, right={new_right_check}, bottom={new_bottom_check}")

            self._redraw_selection()
            
            # 检查redraw后bbox是否被改变
            final_bbox = self._selection_bbox
            if final_bbox != (new_left_check, new_top_check, new_right_check, new_bottom_check):
                debug_log(f"[警告] redraw后bbox被改变！设置: {new_left_check},{new_top_check},{new_right_check},{new_bottom_check} -> 实际: {final_bbox}")
            
            debug_log("-" * 60)
            return
        
        # 若拖动的是边框，调整对应边界（固定另外三条边）
        if self._dragging_edge and self._selection_bbox:
            left, top, right, bottom = self._selection_bbox
            min_size = 0
            if self._dragging_edge == 'top':
                new_top = max(0, min(bottom - min_size, event.y))
                self._selection_bbox = (left, new_top, right, bottom)
            elif self._dragging_edge == 'bottom':
                new_bottom = max(0, event.y)
                self._selection_bbox = (left, top, right, new_bottom)
            elif self._dragging_edge == 'left':
                new_left = max(0, min(right - min_size, event.x))
                self._selection_bbox = (new_left, top, right, bottom)
            elif self._dragging_edge == 'right':
                new_right = max(0, event.x)
                self._selection_bbox = (left, top, new_right, bottom)
            debug_log(f"[边框拖动] 边={self._dragging_edge}, 鼠标=({event.x},{event.y}), 新bbox={self._selection_bbox}")
            self._redraw_selection()
            return
        
        # 如果点击在选区内部且没有句柄，则拖动移动选区
        if self._selection_bbox and self._point_in_bbox(self._selection_bbox, event.x, event.y) and not self._active_handle:
            sx0, sy0, sx1, sy1 = self._selection_bbox
            dx = event.x - self._start_xy[0]
            dy = event.y - self._start_xy[1]
            self._selection_bbox = (sx0+dx, sy0+dy, sx1+dx, sy1+dy)
            self._start_xy = (event.x, event.y)
            self._redraw_selection()
            return
        
        # 手动框选模式
        x0, y0 = self._start_xy
        self._selection_bbox = (min(x0, x1), min(y0, y1), max(x0, x1), max(y0, y1))
        self._redraw_selection()

    def _on_sel_release(self, event):
        """鼠标左键释放事件"""
        if not self._click_point:
            # 如果没有记录点击位置，可能是异常情况，重置状态
            self._is_dragging = False
            self._click_point = None
            self._start_xy = None
            return
        
        # 检查是否是点击（无拖动）还是拖动完成
        dx = abs(event.x - self._click_point[0])
        dy = abs(event.y - self._click_point[1])
        is_click = (dx < 3 and dy < 3)  # 小于3像素认为是点击
        
        # 保存是否拖动过（在重置_is_dragging之前）
        was_dragging = self._is_dragging
        
        if is_click and self._was_window_selected:
            # 模式1：点击选择窗口（无拖动）
            # 确认选择，保持框选中状态，锁定选区不再自动匹配窗口
            if self._selection_bbox:
                self._selection_locked = True
        elif was_dragging:
            # 模式2：拖动完成（无论是否有选区框，只要拖动过就锁定，保持在手动模式）
            # 一旦进入拖动模式，就不再自动匹配窗口
            self._selection_locked = True
        # 注意：无论是点击还是拖动，都不自动确认截图，保持框选中状态
        
        # 重置拖动状态，但保持选区框
        self._is_dragging = False
        self._click_point = None
        self._start_xy = None  # 释放后不再跟踪拖动起点
        self._dragging_edge = None  # 重置边框拖动状态
        self._active_handle = None
        self._resize_anchor_bbox = None

    def _on_right_click(self, event):
        # 右键：如果处于待确认（蓝色）状态，则取消并恢复为可拖动红色框
        if self._sel_confirm_armed:
            self._sel_confirm_armed = False
            if self._rect_id:
                self._overlay_canvas.itemconfig(self._rect_id, outline='red')
            return
        # 非待确认（红色编辑态）：直接退出截图模式
        if self._rect_id and not self._sel_confirm_armed:
            debug_log("[右键] 红色编辑态，退出截图模式")
            self._cancel_selection()
            return
        # 兜底：清除句柄状态
        self._active_handle = None

    def _on_double_click(self, event):
        try:
            x, y = event.x, event.y
            bbox = self._selection_bbox
            if bbox and bbox[0] <= x <= bbox[2] and bbox[1] <= y <= bbox[3]:
                # 双击在选区内，直接确认
                self._confirm_selection()
            else:
                # 无有效选区或点击在外，改为全屏
                self._selection_bbox = None
                self._confirm_selection()
        except Exception:
            # 兜底：全屏
            self._selection_bbox = None
            self._confirm_selection()

    def _point_in_bbox(self, bbox, x, y):
        if not bbox:
            return False
        x0, y0, x1, y1 = bbox
        return x0 <= x <= x1 and y0 <= y <= y1

    def _on_mouse_move(self, event):
        # 计算屏幕坐标
        screen_x = self._overlay.winfo_rootx() + event.x
        screen_y = self._overlay.winfo_rooty() + event.y
        
        # 检查是否移动了10像素（启用窗口检测的阈值）
        if not self._auto_window_detection and self._initial_mouse_pos:
            dx = abs(screen_x - self._initial_mouse_pos[0])
            dy = abs(screen_y - self._initial_mouse_pos[1])
            if dx >= 10 or dy >= 10:
                self._auto_window_detection = True
                debug_log(f"[SCREENSHOT] 鼠标移动达到10像素阈值，启用窗口自动检测: dx={dx}, dy={dy}")
        
        # 更新十字线：仅在没有选区框时显示，有选区后隐藏
        w = self._overlay_canvas.winfo_width()
        h = self._overlay_canvas.winfo_height()
        if self._selection_bbox:
            if self._crosshair_h:
                self._overlay_canvas.delete(self._crosshair_h)
                self._crosshair_h = None
            if self._crosshair_v:
                self._overlay_canvas.delete(self._crosshair_v)
                self._crosshair_v = None
        else:
            if self._crosshair_h:
                self._overlay_canvas.delete(self._crosshair_h)
            if self._crosshair_v:
                self._overlay_canvas.delete(self._crosshair_v)
            self._crosshair_h = self._overlay_canvas.create_line(0, event.y, w, event.y, fill='#00aaff', dash=(2, 4))
            self._crosshair_v = self._overlay_canvas.create_line(event.x, 0, event.x, h, fill='#00aaff', dash=(2, 4))
        # 更新放大镜：开始截图后即显示并跟随；选框确定后隐藏
        # 显示条件：
        # 1. 模式1（未锁定）：自动抓取软件界面时，放大镜应该开启
        # 2. 模式2（已锁定）：还未进入蓝色待确认状态时，放大镜应该开启
        # 隐藏条件：
        # 1. 模式1：通过点击窗口框选择了窗口后（_selection_locked=True 且 _was_window_selected=True），选框确定，隐藏放大镜
        # 2. 模式2：进入蓝色待确认状态（_sel_confirm_armed=True）后，隐藏放大镜
        should_show_magnifier = True
        if self._selection_locked and self._was_window_selected:
            # 模式1：已通过点击窗口框选择，选框确定，隐藏放大镜
            should_show_magnifier = False
        elif self._sel_confirm_armed:
            # 模式2：进入蓝色待确认状态，隐藏放大镜
            should_show_magnifier = False
        
        if should_show_magnifier:
            self._update_magnifier(event.x, event.y)
        else:
            # 隐藏放大镜
            if self._magnifier is not None:
                try:
                    self._magnifier.destroy()
                except Exception:
                    pass
                self._magnifier = None
                self._magnifier_canvas = None
                self._magnifier_img = None
        # 检测是否命中句柄
        self._active_handle = self._hit_test_handle(event.x, event.y)
        if self._active_handle:
            curs_map = {
                'n': 'top_side', 's': 'bottom_side', 'w': 'left_side', 'e': 'right_side',
                'nw': 'top_left_corner', 'ne': 'top_right_corner', 'sw': 'bottom_left_corner', 'se': 'bottom_right_corner'
            }
            self._overlay_canvas.configure(cursor=curs_map.get(self._active_handle, 'cross'))
        else:
            # 检测是否在边框上（但不在句柄上）
            edge = self._hit_test_edge(event.x, event.y) if self._selection_bbox else None
            if edge:
                edge_curs_map = {
                    'top': 'top_side',
                    'bottom': 'bottom_side',
                    'left': 'left_side',
                    'right': 'right_side'
                }
                self._overlay_canvas.configure(cursor=edge_curs_map.get(edge, 'cross'))
            else:
                self._overlay_canvas.configure(cursor='cross')
        
        # 窗口检测（需要移动10像素后才开始，并且节流处理）
        now_ts = time.time()
        moved = (self._last_mouse_xy != (screen_x, screen_y))
        should_check = self._auto_window_detection and moved and (now_ts - (self._last_pick_ts or 0)) >= 0.2
        window_rect = None
        if should_check:
            # 仅每200ms做一次拾取与日志
            self._last_pick_ts = now_ts
            self._last_mouse_xy = (screen_x, screen_y)
            debug_log(f"[PICK] 鼠标: ({screen_x},{screen_y})")
            try:
                fg = win32gui.GetForegroundWindow()
                if fg:
                    try:
                        _t, _p = win32process.GetWindowThreadProcessId(fg)
                        _pn = psutil.Process(_p).name().lower() if _p else ''
                    except Exception:
                        _pn = ''
                    debug_log(f"[FOREGROUND] hwnd={fg}, pid={_p}, proc={_pn}")
            except Exception:
                pass
            window_rect = self._get_window_at_point(screen_x, screen_y)

        # 自动窗口检测和匹配（仅在未开始拖动框选且选区未锁定时，才更新UI）
        if window_rect and not self._is_dragging and not self._selection_locked and self._auto_window_detection:
            if window_rect:
                # 将窗口矩形转换为画布坐标
                x0 = window_rect[0] - self._overlay.winfo_rootx()
                y0 = window_rect[1] - self._overlay.winfo_rooty()
                x1 = window_rect[2] - self._overlay.winfo_rootx()
                y1 = window_rect[3] - self._overlay.winfo_rooty()
                # 检查鼠标是否在窗口范围内
                if x0 <= event.x <= x1 and y0 <= event.y <= y1:
                    # 只有当窗口框不同时才更新（避免频繁重绘）
                    current_bbox = self._selection_bbox
                    if not current_bbox or abs(current_bbox[0] - x0) > 2 or abs(current_bbox[1] - y0) > 2 or \
                       abs(current_bbox[2] - x1) > 2 or abs(current_bbox[3] - y1) > 2:
                        # 只有未手动选择窗口时才自动更新
                        if not self._was_window_selected:
                            self._selection_bbox = (x0, y0, x1, y1)
                            self._last_detected_window = window_rect
                            self._redraw_selection()
                    elif window_rect != self._last_detected_window:
                        # 窗口不同但框相同，更新记录（只有未手动选择时）
                        if not self._was_window_selected:
                            self._last_detected_window = window_rect

    def _update_magnifier(self, cx, cy):
        try:
            from PIL import ImageTk
            if not hasattr(self, '_screen_image') or self._screen_image is None:
                return
            half = self._magnifier_size // (2 * self._magnifier_zoom)
            x0 = max(0, cx - half)
            y0 = max(0, cy - half)
            x1 = min(self._screen_image.width, cx + half)
            y1 = min(self._screen_image.height, cy + half)
            crop = self._screen_image.crop((x0, y0, x1, y1)).resize((self._magnifier_size, self._magnifier_size))
            self._magnifier_img = ImageTk.PhotoImage(crop)
            if self._magnifier is None:
                self._magnifier = tk.Toplevel(self._overlay)
                self._magnifier.overrideredirect(True)
                self._magnifier.attributes('-topmost', True)
                self._magnifier_canvas = tk.Canvas(self._magnifier, width=self._magnifier_size+2, height=self._magnifier_size+2, highlightthickness=1, highlightbackground='#00aaff')
                self._magnifier_canvas.pack()
            # 放置在鼠标附近
            self._magnifier.geometry(f"+{self._overlay.winfo_rootx()+cx+16}+{self._overlay.winfo_rooty()+cy+16}")
            self._magnifier_canvas.delete('all')
            self._magnifier_canvas.create_image(1, 1, anchor='nw', image=self._magnifier_img)
            # 画中心十字
            m = self._magnifier_size//2
            self._magnifier_canvas.create_line(m, 0, m, self._magnifier_size, fill='yellow')
            self._magnifier_canvas.create_line(0, m, self._magnifier_size, m, fill='yellow')
        except Exception:
            pass

    def _update_mask(self):
        """更新半透明遮罩：框外50%透明，框内高亮"""
        # 清除旧的遮罩
        for pid in self._mask_polygons:
            self._overlay_canvas.delete(pid)
        self._mask_polygons = []
        
        # 删除旧的遮罩图像
        if getattr(self, '_mask_image_id', None):
            try:
                self._overlay_canvas.delete(self._mask_image_id)
                self._mask_image_id = None
            except Exception:
                pass
        
        try:
            canvas_w = self._overlay_canvas.winfo_width()
            canvas_h = self._overlay_canvas.winfo_height()
            if canvas_w <= 1 or canvas_h <= 1:
                # 如果画布尺寸还未确定，使用屏幕尺寸
                try:
                    canvas_w = self.root.winfo_screenwidth()
                    canvas_h = self.root.winfo_screenheight()
                except:
                    return
        except:
            try:
                canvas_w = self.root.winfo_screenwidth()
                canvas_h = self.root.winfo_screenheight()
            except:
                return
        
        # 使用PIL生成半透明遮罩图像（无网格，纯半透明）
        try:
            from PIL import Image, ImageDraw, ImageTk
            
            # 如果没有选区框，显示全屏半透明遮罩（50%透明黑色）
            if not self._selection_bbox:
                mask_img = Image.new('RGBA', (canvas_w, canvas_h), (0, 0, 0, 128))  # 50%透明
            else:
                # 有选区框：框外半透明，框内透明
                x0, y0, x1, y1 = self._selection_bbox
                # 确保坐标在画布范围内
                x0 = max(0, min(x0, canvas_w))
                y0 = max(0, min(y0, canvas_h))
                x1 = max(0, min(x1, canvas_w))
                y1 = max(0, min(y1, canvas_h))
                mask_img = Image.new('RGBA', (canvas_w, canvas_h), (0, 0, 0, 128))
                draw = ImageDraw.Draw(mask_img)
                # 选区内透明
                draw.rectangle([x0, y0, x1, y1], fill=(0, 0, 0, 0))
            
            self._mask_image_tk = ImageTk.PhotoImage(mask_img)
            self._mask_image_id = self._overlay_canvas.create_image(0, 0, anchor='nw', image=self._mask_image_tk)
            # 确保遮罩在选区框下方
            if self._rect_id:
                try:
                    self._overlay_canvas.tag_lower(self._mask_image_id, self._rect_id)
                except:
                    pass
        except Exception as e:
            debug_log(f"[MASK] Error creating mask: {e}")
            # 兜底：使用简单矩形（不透明）
            if not self._selection_bbox:
                mask_id = self._overlay_canvas.create_rectangle(0, 0, canvas_w, canvas_h, fill='#80000000', outline='')
                self._mask_polygons.append(mask_id)
            else:
                x0, y0, x1, y1 = self._selection_bbox
                x0 = max(0, min(x0, canvas_w))
                y0 = max(0, min(y0, canvas_h))
                x1 = max(0, min(x1, canvas_w))
                y1 = max(0, min(y1, canvas_h))
                if y0 > 0:
                    self._mask_polygons.append(self._overlay_canvas.create_rectangle(0, 0, canvas_w, y0, fill='#80000000', outline=''))
                if y1 < canvas_h:
                    self._mask_polygons.append(self._overlay_canvas.create_rectangle(0, y1, canvas_w, canvas_h, fill='#80000000', outline=''))
                if x0 > 0:
                    self._mask_polygons.append(self._overlay_canvas.create_rectangle(0, y0, x0, y1, fill='#80000000', outline=''))
                if x1 < canvas_w:
                    self._mask_polygons.append(self._overlay_canvas.create_rectangle(x1, y0, canvas_w, y1, fill='#80000000', outline=''))
        except Exception:
            pass

    def _update_size_label(self):
        if not self._selection_bbox:
            if self._size_label_id:
                self._overlay_canvas.delete(self._size_label_id)
                self._size_label_id = None
            return
        x0, y0, x1, y1 = self._selection_bbox
        w = max(0, x1 - x0)
        h = max(0, y1 - y0)
        text = f"{w}×{h}"
        if self._size_label_id:
            self._overlay_canvas.delete(self._size_label_id)
        self._size_label_id = self._overlay_canvas.create_text(
            x0 + 8,
            max(0, y0 - 8),
            text=text,
            anchor='sw',
            fill='white',
            font=('Microsoft YaHei', 10, 'bold')
        )

    def _draw_resize_handles(self):
        # 清除旧
        for hid in self._resize_handles:
            self._overlay_canvas.delete(hid)
        self._resize_handles = []
        if not self._selection_bbox:
            return
        x0, y0, x1, y1 = self._selection_bbox
        pts = {
            'nw': (x0, y0), 'n': ((x0+x1)//2, y0), 'ne': (x1, y0),
            'w': (x0, (y0+y1)//2),               'e': (x1, (y0+y1)//2),
            'sw': (x0, y1), 's': ((x0+x1)//2, y1), 'se': (x1, y1)
        }
        r = 4
        for key, (cx, cy) in pts.items():
            hid = self._overlay_canvas.create_rectangle(cx-r, cy-r, cx+r, cy+r, fill='#00aaff', outline='white')
            self._overlay_canvas.addtag_withtag(f'handle_{key}', hid)
            self._resize_handles.append(hid)

    def _hit_test_handle(self, x, y):
        if not self._selection_bbox:
            return None
        # 简单距离检测
        x0, y0, x1, y1 = self._selection_bbox
        pts = {
            'nw': (x0, y0), 'n': ((x0+x1)//2, y0), 'ne': (x1, y0),
            'w': (x0, (y0+y1)//2),               'e': (x1, (y0+y1)//2),
            'sw': (x0, y1), 's': ((x0+x1)//2, y1), 'se': (x1, y1)
        }
        for key, (cx, cy) in pts.items():
            if abs(x - cx) <= 6 and abs(y - cy) <= 6:
                return key
        return None
    
    def _hit_test_edge(self, x, y):
        """检测点击是否在选区边框上（但不在句柄和框内）"""
        if not self._selection_bbox:
            return None
        
        # 先检查是否在句柄上，如果在句柄上则返回None（句柄由_hit_test_handle处理）
        if self._hit_test_handle(x, y):
            return None
        
        x0, y0, x1, y1 = self._selection_bbox
        edge_threshold = 5  # 边框检测阈值（像素）
        handle_size = 6  # 句柄大小（像素），避免在句柄附近误判为边框
        
        # 检查是否在框内（排除边框区域）
        if x0 + edge_threshold < x < x1 - edge_threshold and y0 + edge_threshold < y < y1 - edge_threshold:
            return None  # 在框内，不是边框
        
        # 检查是否在上边框（排除角和中间句柄区域）
        if abs(y - y0) <= edge_threshold and x0 <= x <= x1:
            # 排除左右两个角的句柄区域
            if abs(x - x0) <= handle_size or abs(x - x1) <= handle_size:
                return None
            # 排除中间的句柄（上边框中间）
            mid_x = (x0 + x1) // 2
            if abs(x - mid_x) <= handle_size:
                return None
            return 'top'
        # 检查是否在下边框
        if abs(y - y1) <= edge_threshold and x0 <= x <= x1:
            # 排除左右两个角的句柄区域
            if abs(x - x0) <= handle_size or abs(x - x1) <= handle_size:
                return None
            # 排除中间的句柄（下边框中间）
            mid_x = (x0 + x1) // 2
            if abs(x - mid_x) <= handle_size:
                return None
            return 'bottom'
        # 检查是否在左边框（排除角和中间句柄区域）
        if abs(x - x0) <= edge_threshold and y0 <= y <= y1:
            # 排除上下两个角的句柄区域
            if abs(y - y0) <= handle_size or abs(y - y1) <= handle_size:
                return None
            # 排除中间的句柄（左边框中间）
            mid_y = (y0 + y1) // 2
            if abs(y - mid_y) <= handle_size:
                return None
            return 'left'
        # 检查是否在右边框
        if abs(x - x1) <= edge_threshold and y0 <= y <= y1:
            # 排除上下两个角的句柄区域
            if abs(y - y0) <= handle_size or abs(y - y1) <= handle_size:
                return None
            # 排除中间的句柄（右边框中间）
            mid_y = (y0 + y1) // 2
            if abs(y - mid_y) <= handle_size:
                return None
            return 'right'
        
        return None

    def _get_window_at_point(self, x, y):
        """获取指定屏幕坐标点的窗口矩形"""
        try:
            debug_log(f"[PICK] 查询点: ({int(x)},{int(y)})")
            px, py = int(x), int(y)

            overlay_hwnd = self._overlay.winfo_id()
            current_pid = os.getpid()

            # 定义过滤与判定
            explorer_classes = {
                'Progman', 'WorkerW', 'Shell_TrayWnd', 'TrayNotifyWnd', 'Button'
            }

            def is_reasonable_window(h):
                try:
                    if not win32gui.IsWindowVisible(h):
                        return False
                    style = win32gui.GetWindowLong(h, win32con.GWL_EXSTYLE)
                    # 跳过工具/透明窗口
                    if style & win32con.WS_EX_TOOLWINDOW:
                        return False
                    rect = win32gui.GetWindowRect(h)
                    w = rect[2] - rect[0]
                    h_val = rect[3] - rect[1]
                    if w <= 20 or h_val <= 20:
                        return False
                    return True
                except Exception:
                    return False

            def should_skip(h):
                try:
                    if h == overlay_hwnd:
                        return 'overlay'
                    _tid, pid = win32process.GetWindowThreadProcessId(h)
                    if pid == current_pid:
                        return 'same_pid'
                    cls = win32gui.GetClassName(h)
                    if cls in ('TkTopLevel', 'TkChild'):
                        return f'class:{cls}'
                    pname = ''
                    if pid:
                        try:
                            pname = psutil.Process(pid).name().lower()
                        except Exception:
                            pname = ''
                    if pname in ('windowspygui.exe', 'python.exe', 'pythonw.exe'):
                        return f'proc:{pname}'
                    # 模式1（未锁定）时允许explorer，模式2（已锁定）时过滤explorer
                    if self._selection_locked and (cls in explorer_classes or pname == 'explorer.exe'):
                        return 'explorer'
                except Exception:
                    return None
                return None

            # 方案：从Z序最顶层开始向下遍历所有顶层窗口，命中点即返回
            try:
                top = win32gui.GetTopWindow(None)
            except Exception:
                top = None
            safety = 0
            curr = top
            best = None
            while curr and safety < 4096:
                safety += 1
                try:
                    # 先做基础过滤
                    if not is_reasonable_window(curr):
                        curr = win32gui.GetWindow(curr, win32con.GW_HWNDNEXT)
                        continue
                    rect = win32gui.GetWindowRect(curr)
                    if not (rect[0] <= px <= rect[2] and rect[1] <= py <= rect[3]):
                        curr = win32gui.GetWindow(curr, win32con.GW_HWNDNEXT)
                        continue
                    skip = should_skip(curr)
                    if skip:
                        debug_log(f"[SKIP] {skip} hwnd={curr}")
                        curr = win32gui.GetWindow(curr, win32con.GW_HWNDNEXT)
                        continue

                    # 命中：记录并退出（第一个命中即为最前层）
                    best = curr
                    break
                except Exception:
                    curr = win32gui.GetWindow(curr, win32con.GW_HWNDNEXT)
                    continue

            if not best:
                return None

            # 可选：在该顶层窗口下，找更靠前的子窗口（若需要更精准控件级捕捉）
            try:
                child = win32gui.WindowFromPoint((px, py))
                if child and win32gui.IsWindow(child):
                    # 确保 child 隶属于 best
                    anc = child
                    GA_ROOT = 2
                    try:
                        anc_root = win32gui.GetAncestor(child, GA_ROOT)
                    except Exception:
                        anc_root = None
                    if anc_root == best and is_reasonable_window(child):
                        best = child
            except Exception:
                pass

            try:
                rect = win32gui.GetWindowRect(best)
                cls = ''
                try:
                    cls = win32gui.GetClassName(best)
                except Exception:
                    cls = ''
                debug_log(f"[WINDOW] 命中窗口: hwnd={best}, class={cls}, rect={rect}")
                return rect
            except Exception:
                return None
        except Exception:
            return None

    def _redraw_selection(self):
        if not self._selection_bbox:
            # 清除遮罩
            for pid in self._mask_polygons:
                self._overlay_canvas.delete(pid)
            self._mask_polygons = []
            if self._rect_id:
                self._overlay_canvas.delete(self._rect_id)
                self._rect_id = None
            return
        x0, y0, x1, y1 = self._selection_bbox
        if self._rect_id:
            self._overlay_canvas.coords(self._rect_id, x0, y0, x1, y1)
        else:
            self._rect_id = self._overlay_canvas.create_rectangle(x0, y0, x1, y1, outline='red', width=2)
        # 状态颜色
        self._overlay_canvas.itemconfig(self._rect_id, outline='blue' if self._sel_confirm_armed else 'red')
        self._update_size_label()
        self._draw_resize_handles()
        # 更新半透明遮罩（框外50%透明，框内高亮）
        self._update_mask()

    def _cancel_selection(self):
        if self._overlay is not None:
            self._overlay.destroy()
        self._overlay = None
        self._overlay_canvas = None
        
        # 取消截图后，恢复主窗口显示
        if self.config.get_screenshot_hide_main_window():
            self._show_from_tray()
        # 关闭放大镜
        if self._magnifier is not None:
            try:
                self._magnifier.destroy()
            except Exception:
                pass
        self._magnifier = None
        self._magnifier_canvas = None
        self._magnifier_img = None
        self._start_xy = None
        self._rect_id = None
        self._selection_bbox = None
        self._mask_polygons = []
        self._last_detected_window = None
        self._is_dragging = False
        self._click_point = None
        self._was_window_selected = False
        self._selection_locked = False
        self._dragging_edge = None
        self._active_handle = None
        self._resize_anchor_bbox = None

    def _confirm_selection(self):
        bbox = self._selection_bbox
        self._cancel_selection()
        
        # 截图确认后，恢复主窗口显示并切换到截图页签
        if self.config.get_screenshot_hide_main_window():
            self._show_from_tray()
        
        # 切换到截图页签（索引2），但不保存到配置
        try:
            self._skip_tab_save = True  # 标记为程序自动切换，不保存
            self.notebook.select(2)  # 截图页签是第三个，索引为2
            self.root.deiconify()  # 确保窗口显示
            self.root.lift()  # 置顶
            self.root.focus_force()  # 获取焦点
        except Exception as e:
            debug_log(f"[SCREENSHOT] Error switching to screenshot tab: {e}")
            self._skip_tab_save = False  # 确保标志被重置
        try:
            from PIL import Image, ImageTk
            import io
            # 若没有有效选区，使用整屏图像；否则从缓存屏幕图裁剪
            if not hasattr(self, '_screen_image') or self._screen_image is None:
                return
            if not bbox or bbox[2] - bbox[0] <= 1 or bbox[3] - bbox[1] <= 1:
                img = self._screen_image.copy()
            else:
                # 防止越界
                x0, y0, x1, y1 = [max(0, int(v)) for v in bbox]
                x1 = min(x1, self._screen_image.width)
                y1 = min(y1, self._screen_image.height)
                if x1 <= x0 or y1 <= y0:
                    img = self._screen_image.copy()
                else:
                    img = self._screen_image.crop((x0, y0, x1, y1))
            # 存储最近截图
            self._last_screenshot = img.copy()
            # 更新历史（最多9张，最新在前）
            if self._shot_history is None:
                self._shot_history = []
            self._shot_history.insert(0, self._last_screenshot.copy())
            if len(self._shot_history) > 9:
                self._shot_history = self._shot_history[:9]
            self._update_thumbnail_history()
            # 显示缩略图
            thumb = img.copy()
            thumb.thumbnail((600, 400))
            self._thumb_image = ImageTk.PhotoImage(thumb)
            self.screenshot_thumb_label.configure(image=self._thumb_image)
            # 复制到剪贴板（DIB）
            self._copy_image_to_clipboard(img)
            try:
                self.log_task("截图已复制到剪贴板")
            except Exception:
                pass
        except Exception as e:
            try:
                self.log_task(f"截图失败: {e}")
            except Exception:
                pass

    def _copy_image_to_clipboard(self, pil_image):
        """将PIL图片复制到Windows剪贴板为CF_DIB"""
        import win32clipboard
        import win32con
        import io
        from PIL import Image
        # 转为DIB（BMP去掉14字节的文件头）
        output = io.BytesIO()
        # 确保为RGB或RGBA
        if pil_image.mode not in ("RGB", "RGBA"):
            pil_image = pil_image.convert("RGB")
        pil_image.save(output, format='BMP')
        data = output.getvalue()[14:]
        output.close()
        win32clipboard.OpenClipboard()
        try:
            win32clipboard.EmptyClipboard()
            win32clipboard.SetClipboardData(win32con.CF_DIB, data)
        finally:
            win32clipboard.CloseClipboard()

    def _open_annotate_window(self):
        # 简单标注：矩形、箭头
        if self._last_screenshot is None:
            messagebox.showwarning('提示', '请先截图后再标注')
            return
        win = tk.Toplevel(self.root)
        win.title('标注')
        from PIL import ImageTk, Image, ImageDraw
        self._anno_img = self._last_screenshot.copy()
        self._anno_draw = ImageDraw.Draw(self._anno_img)
        self._anno_tool = tk.StringVar(value='rect')
        toolbar = ttk.Frame(win)
        toolbar.pack(fill=tk.X)
        ttk.Radiobutton(toolbar, text='矩形', variable=self._anno_tool, value='rect').pack(side=tk.LEFT, padx=4, pady=4)
        ttk.Radiobutton(toolbar, text='箭头', variable=self._anno_tool, value='arrow').pack(side=tk.LEFT, padx=4, pady=4)
        ttk.Button(toolbar, text='完成', command=lambda: (self._close_annotate(win))).pack(side=tk.RIGHT, padx=4)
        self._anno_canvas = tk.Canvas(win, bg='black')
        self._anno_canvas.pack(fill=tk.BOTH, expand=True)
        self._anno_tk = ImageTk.PhotoImage(self._anno_img)
        self._anno_canvas_img = self._anno_canvas.create_image(0,0, anchor='nw', image=self._anno_tk)
        self._anno_start = None
        self._anno_temp = None
        self._anno_canvas.config(scrollregion=(0,0,self._anno_img.width, self._anno_img.height))
        self._anno_canvas.bind('<Button-1>', self._anno_on_press)
        self._anno_canvas.bind('<B1-Motion>', self._anno_on_drag)
        self._anno_canvas.bind('<ButtonRelease-1>', self._anno_on_release)

    def _anno_on_press(self, e):
        self._anno_start = (e.x, e.y)
        if self._anno_temp:
            self._anno_canvas.delete(self._anno_temp)
            self._anno_temp = None

    def _anno_on_drag(self, e):
        if not self._anno_start:
            return
        x0, y0 = self._anno_start
        x1, y1 = e.x, e.y
        if self._anno_temp:
            self._anno_canvas.delete(self._anno_temp)
        if self._anno_tool.get() == 'rect':
            self._anno_temp = self._anno_canvas.create_rectangle(x0, y0, x1, y1, outline='red', width=2)
        else:
            self._anno_temp = self._anno_canvas.create_line(x0, y0, x1, y1, fill='red', width=3, arrow=tk.LAST)

    def _anno_on_release(self, e):
        if not self._anno_start:
            return
        x0, y0 = self._anno_start
        x1, y1 = e.x, e.y
        from PIL import ImageDraw
        draw = self._anno_draw
        if self._anno_tool.get() == 'rect':
            draw.rectangle([x0, y0, x1, y1], outline='red', width=3)
        else:
            draw.line([x0, y0, x1, y1], fill='red', width=4)
        self._anno_start = None
        if self._anno_temp:
            self._anno_canvas.delete(self._anno_temp)
            self._anno_temp = None
        # 更新底图
        from PIL import ImageTk
        self._anno_tk = ImageTk.PhotoImage(self._anno_img)
        self._anno_canvas.itemconfigure(self._anno_canvas_img, image=self._anno_tk)

    def _close_annotate(self, win):
        # 保存回最近截图并更新缩略图
        self._last_screenshot = self._anno_img.copy()
        try:
            from PIL import ImageTk
            thumb = self._last_screenshot.copy(); thumb.thumbnail((600, 400))
            self._thumb_image = ImageTk.PhotoImage(thumb)
            self.screenshot_thumb_label.configure(image=self._thumb_image)
        except Exception:
            pass
        if self._shot_history is not None and len(self._shot_history)>0:
            self._shot_history[0] = self._last_screenshot.copy()
            self._update_thumbnail_history()
        win.destroy()

    def _update_thumbnail_history(self):
        try:
            from PIL import ImageTk
        except Exception:
            return
        # 清理旧的PhotoImage防止被GC
        self._shot_thumbs = []
        for i in range(9):
            lbl = self._thumb_labels[i]
            if i < len(self._shot_history):
                im = self._shot_history[i]
                t = im.copy()
                t.thumbnail((220, 140))
                tkimg = ImageTk.PhotoImage(t)
                self._shot_thumbs.append(tkimg)
                lbl.configure(image=tkimg, text="")
            else:
                lbl.configure(image="", text=f"{i+1}")

    def _on_thumb_click(self, index):
        if index >= len(self._shot_history):
            return
        img = self._shot_history[index]
        # 复制到剪贴板并作为主图显示
        try:
            self._copy_image_to_clipboard(img)
            from PIL import ImageTk
            main_thumb = img.copy()
            main_thumb.thumbnail((600, 400))
            self._thumb_image = ImageTk.PhotoImage(main_thumb)
            self.screenshot_thumb_label.configure(image=self._thumb_image)
            try:
                self.log_task(f"已从历史第{index+1}张复制到剪贴板")
            except Exception:
                pass
        except Exception as e:
            try:
                self.log_task(f"复制历史截图失败: {e}")
            except Exception:
                pass

    def _on_thumb_menu(self, event, index):
        if index >= len(self._shot_history):
            return
        menu = tk.Menu(self.root, tearoff=0)
        menu.add_command(label='复制到剪贴板', command=lambda: self._on_thumb_click(index))
        menu.add_command(label='保存为...', command=lambda: self._save_history_image(index))
        menu.add_command(label='置顶此图', command=lambda: self._pin_history_image(index))
        menu.add_command(label='删除此图', command=lambda: self._delete_history_image(index))
        try:
            menu.tk_popup(event.x_root, event.y_root)
        finally:
            menu.grab_release()

    def _save_history_image(self, index):
        from tkinter import filedialog
        if index >= len(self._shot_history):
            return
        img = self._shot_history[index]
        filetypes = [("PNG 图片", "*.png"),("JPEG 图片", "*.jpg;*.jpeg"),("BMP 图片", "*.bmp"),("所有文件", "*.*")]
        path = filedialog.asksaveasfilename(defaultextension='.png', filetypes=filetypes, title='保存历史截图')
        if not path:
            return
        ext = os.path.splitext(path)[1].lower()
        fmt = 'PNG'
        if ext in ('.jpg', '.jpeg'):
            fmt = 'JPEG'
        elif ext == '.bmp':
            fmt = 'BMP'
        img.save(path, format=fmt)

    def _pin_history_image(self, index):
        if index >= len(self._shot_history):
            return
        img = self._shot_history.pop(index)
        self._shot_history.insert(0, img)
        self._update_thumbnail_history()

    def _delete_history_image(self, index):
        if index >= len(self._shot_history):
            return
        self._shot_history.pop(index)
        self._update_thumbnail_history()

    def save_screenshot_to_file(self):
        """保存最近一次截图到文件"""
        try:
            if self._last_screenshot is None:
                messagebox.showwarning("提示", "暂无可保存的截图，请先进行截图")
                return
            filetypes = [
                ("PNG 图片", "*.png"),
                ("JPEG 图片", "*.jpg;*.jpeg"),
                ("BMP 图片", "*.bmp"),
                ("所有文件", "*.*"),
            ]
            default_name = time.strftime("screenshot_%Y%m%d_%H%M%S.png")
            path = filedialog.asksaveasfilename(defaultextension=".png", initialfile=default_name,
                                                filetypes=filetypes, title="保存截图")
            if not path:
                return
            # 根据扩展名选择格式
            ext = os.path.splitext(path)[1].lower()
            fmt = 'PNG'
            if ext in ('.jpg', '.jpeg'):
                fmt = 'JPEG'
            elif ext == '.bmp':
                fmt = 'BMP'
            self._last_screenshot.save(path, format=fmt)
            try:
                self.log_task(f"截图已保存: {path}")
            except Exception:
                pass
        except Exception as e:
            messagebox.showerror("错误", f"保存截图失败: {e}")
    
    def refresh_window_list(self):
        """刷新窗口列表"""
        try:
            # 清空现有项目
            for item in self.window_tree.get_children():
                self.window_tree.delete(item)
            
            # 获取所有窗口
            self.spy.refresh_windows()
            
            # 添加到树视图
            for window in self.spy.windows:
                self.window_tree.insert('', 'end', 
                                      text=window.title,
                                      values=(window.class_name, window.process_id, 
                                             window.process_name, window.exe_path),
                                      tags=(window.hwnd,))
            
            self.info_text.delete(1.0, tk.END)
            self.info_text.insert(tk.END, f"找到 {len(self.spy.windows)} 个窗口\n")
            
            # 重置排序状态
            self.sort_column = None
            self.sort_reverse = False
            self.update_column_headers()
            
        except Exception as e:
            messagebox.showerror("错误", f"刷新窗口列表时出错: {e}")
    
    def on_search_change(self, *args):
        """搜索框内容变化时的处理"""
        search_text = self.search_var.get().lower()
        
        # 清空现有项目
        for item in self.window_tree.get_children():
            self.window_tree.delete(item)
        
        # 过滤窗口
        filtered_windows = []
        for window in self.spy.windows:
            if (search_text in window.title.lower() or 
                search_text in window.class_name.lower() or 
                search_text in window.process_name.lower()):
                filtered_windows.append(window)
        
        # 添加到树视图
        for window in filtered_windows:
            self.window_tree.insert('', 'end', 
                                  text=window.title,
                                  values=(window.class_name, window.process_id, 
                                         window.process_name, window.exe_path),
                                  tags=(window.hwnd,))
    
    def on_window_select(self, event):
        """窗口选择事件处理"""
        selection = self.window_tree.selection()
        if not selection:
            return
        
        # 获取选中的窗口句柄
        item = selection[0]
        hwnd = int(self.window_tree.item(item, 'tags')[0])
        
        # 查找对应的窗口信息
        selected_window = None
        for window in self.spy.windows:
            if window.hwnd == hwnd:
                selected_window = window
                break
        
        if selected_window:
            self.show_window_details(selected_window)
    
    def show_window_details(self, window: WindowInfo):
        """显示窗口详细信息"""
        # 清空控件列表
        for item in self.control_tree.get_children():
            self.control_tree.delete(item)
        
        # 添加控件到列表
        for control in window.controls:
            rect_str = f"({control.rect[0]},{control.rect[1]})-({control.rect[2]},{control.rect[3]})"
            self.control_tree.insert('', 'end',
                                   text=control.text or f"[{control.class_name}]",
                                   values=(control.class_name, control.control_id, rect_str),
                                   tags=(control.hwnd,))
        
        # 显示详细信息
        self.info_text.delete(1.0, tk.END)
        info = f"""窗口详细信息:
标题: {window.title}
类名: {window.class_name}
句柄: {window.hwnd}
进程ID: {window.process_id}
进程名: {window.process_name}
位置: ({window.rect[0]}, {window.rect[1]}) - ({window.rect[2]}, {window.rect[3]})
大小: {window.rect[2] - window.rect[0]} x {window.rect[3] - window.rect[1]}
控件数量: {len(window.controls)}

控件列表:
"""
        
        for i, control in enumerate(window.controls, 1):
            info += f"{i}. {control.text or '[无文本]'} ({control.class_name}) - ID: {control.control_id}\n"
            info += f"   句柄: {control.hwnd}, 位置: {control.rect}\n\n"
        
        self.info_text.insert(tk.END, info)
    
    def on_window_right_click(self, event):
        """处理窗口列表右击事件"""
        # 获取点击的项目
        item = self.window_tree.identify_row(event.y)
        if not item:
            return
        
        # 获取窗口信息
        window_text = self.window_tree.item(item, 'text')
        values = self.window_tree.item(item, 'values')
        
        if not values or len(values) < 4:
            return
        
        process_name = values[2]  # 进程名在第三列
        exe_path = values[3]      # exe路径在第四列
        
        # 创建右击菜单
        context_menu = tk.Menu(self.root, tearoff=0)
        context_menu.add_command(
            label=f"添加进程名: {process_name}",
            command=lambda: self.add_process_from_context_menu(process_name)
        )
        
        # 添加进程ID选项
        values = self.window_tree.item(item, 'values')
        if values and len(values) >= 2:
            process_id = values[1]  # 进程ID在第二列
            if process_id and str(process_id).isdigit():
                context_menu.add_command(
                    label=f"添加进程ID: {process_id}",
                    command=lambda: self.add_process_id_from_context_menu(int(process_id))
                )
        
        # 显示菜单
        try:
            context_menu.tk_popup(event.x_root, event.y_root)
        finally:
            context_menu.grab_release()
    
    def add_process_from_context_menu(self, process_name):
        """从右击菜单添加进程到监控列表"""
        if not process_name:
            return
        
        # 检查是否已经存在
        for item in self.task_processes:
            if item['name'] == process_name and item['type'] == 'name':
                messagebox.showinfo("提示", f"进程名 '{process_name}' 已经在监控列表中")
                return
        
        # 添加到列表
        self.task_processes.append({'name': process_name, 'type': 'name'})
        self.task_tree.insert('', 'end', text=process_name, 
                             values=('进程名', '等待', '未检查'))
        
        # 保存配置
        self.save_config()
        
        # 记录日志
        self.log_task(f"从窗口列表添加进程: {process_name}")
        
        # 切换到任务页签
        self.notebook.select(1)  # 切换到第二个页签（任务页签）
    
    def add_process_id_from_context_menu(self, process_id):
        """从右击菜单添加进程ID到监控列表"""
        if not process_id:
            return
        
        # 检查进程ID是否存在
        if not psutil.pid_exists(process_id):
            messagebox.showwarning("警告", f"进程ID {process_id} 不存在")
            return
        
        # 检查是否已经存在
        for item in self.task_processes:
            if item['name'] == str(process_id) and item['type'] == 'pid':
                messagebox.showinfo("提示", f"进程ID '{process_id}' 已经在监控列表中")
                return
        
        # 添加到列表
        self.task_processes.append({'name': str(process_id), 'type': 'pid'})
        self.task_tree.insert('', 'end', text=str(process_id), 
                             values=('进程ID', '等待', '未检查'), tags=('pid_item',))
        
        # 保存配置
        self.save_config()
        
        # 记录日志
        self.log_task(f"从窗口列表添加进程ID: {process_id}")
        
        # 切换到任务页签
        self.notebook.select(1)  # 切换到第二个页签（任务页签）
    
    def toggle_monitoring(self):
        """切换监控状态"""
        if self.spy.is_monitoring:
            self.spy.stop_monitoring()
            self.monitor_btn.config(text="开始监控")
            self.info_text.insert(tk.END, "监控已停止\n")
        else:
            self.spy.start_monitoring(self.on_monitor_callback)
            self.monitor_btn.config(text="停止监控")
            self.info_text.insert(tk.END, "开始监控窗口变化...\n")
    
    def on_monitor_callback(self, new_windows, old_windows):
        """监控回调函数"""
        # 这里可以添加窗口变化检测逻辑
        pass
    
    def add_process_by_name(self):
        """添加进程名到监控列表"""
        process_name = self.process_name_var.get().strip()
        if not process_name:
            messagebox.showwarning("警告", "请输入进程名")
            return
        
        # 检查是否已经存在
        for item in self.task_processes:
            if item['name'] == process_name and item['type'] == 'name':
                messagebox.showinfo("提示", f"进程名 '{process_name}' 已经在监控列表中")
                return
        
        # 添加到列表
        self.task_processes.append({'name': process_name, 'type': 'name'})
        self.task_tree.insert('', 'end', text=process_name, 
                             values=('进程名', '等待', '未检查'))
        
        # 保存配置
        self.save_config()
        
        # 记录日志
        self.log_task(f"添加进程名: {process_name}")
        
        # 清空输入框
        self.process_name_var.set("")
    
    def add_process_by_id(self):
        """添加进程ID到监控列表"""
        process_id_str = self.process_id_var.get().strip()
        if not process_id_str:
            messagebox.showwarning("警告", "请输入进程ID")
            return
        
        try:
            process_id = int(process_id_str)
        except ValueError:
            messagebox.showerror("错误", "进程ID必须是数字")
            return
        
        # 检查进程ID是否存在
        if not psutil.pid_exists(process_id):
            messagebox.showwarning("警告", f"进程ID {process_id} 不存在")
            return
        
        # 检查是否已经存在
        for item in self.task_processes:
            if item['name'] == str(process_id) and item['type'] == 'pid':
                messagebox.showinfo("提示", f"进程ID '{process_id}' 已经在监控列表中")
                return
        
        # 添加到列表
        self.task_processes.append({'name': str(process_id), 'type': 'pid'})
        self.task_tree.insert('', 'end', text=str(process_id), 
                             values=('进程ID', '等待', '未检查'), tags=('pid_item',))
        
        # 保存配置
        self.save_config()
        
        # 记录日志
        self.log_task(f"添加进程ID: {process_id}")
        
        # 清空输入框
        self.process_id_var.set("")
    
    def add_process(self):
        """兼容旧按钮：添加进程名到监控列表"""
        self.add_process_by_name()
    
    def delete_process(self):
        """删除选中的进程"""
        selection = self.task_tree.selection()
        if not selection:
            messagebox.showwarning("警告", "请选择要删除的进程")
            return
        
        for item in selection:
            process_name = self.task_tree.item(item, 'text')
            values = self.task_tree.item(item, 'values')
            process_type = values[0] if values else 'name'
            
            self.task_tree.delete(item)
            
            # 从任务列表中删除
            for i, task_item in enumerate(self.task_processes):
                if task_item['name'] == process_name and task_item['type'] == process_type:
                    del self.task_processes[i]
                    break
            
            self.log_task(f"删除进程: {process_name} ({process_type})")
        
        # 保存配置
        self.save_config()
    
    def start_task_monitoring(self):
        """开始任务监控"""
        if not self.task_processes:
            messagebox.showwarning("警告", "请先添加要监控的进程")
            return
        
        try:
            self.task_interval = int(self.interval_var.get())
            if self.task_interval <= 0:
                raise ValueError("间隔必须大于0")
        except ValueError as e:
            messagebox.showerror("错误", f"无效的时间间隔: {e}")
            return
        
        # 保存配置
        self.save_config()
        
        self.task_running = True
        self.task_start_btn.config(state=tk.DISABLED)
        self.task_stop_btn.config(state=tk.NORMAL)
        
        # 启动监控线程
        self.task_thread = threading.Thread(target=self.task_monitor_loop, daemon=True)
        self.task_thread.start()
        
        self.log_task(f"开始任务监控，间隔: {self.task_interval}秒")
    
    def stop_task_monitoring(self):
        """停止任务监控"""
        self.task_running = False
        self.task_start_btn.config(state=tk.NORMAL)
        self.task_stop_btn.config(state=tk.DISABLED)
        
        self.log_task("停止任务监控")
    
    def task_monitor_loop(self):
        """任务监控循环"""
        while self.task_running:
            try:
                self.check_processes()
                time.sleep(self.task_interval)
            except Exception as e:
                self.log_task(f"监控出错: {e}")
                time.sleep(5)  # 出错后等待5秒再继续
    
    def check_processes(self):
        """检查进程状态"""
        current_time = time.strftime("%H:%M:%S")
        total_terminated = 0
        
        self.log_task(f"开始扫描进程...")
        
        # 先处理进程ID类型的任务
        pid_items = []
        name_items = []
        
        for item in self.task_tree.get_children():
            values = self.task_tree.item(item, 'values')
            process_type = values[0] if values else 'name'
            
            if process_type == '进程ID':
                pid_items.append(item)
            else:
                name_items.append(item)
        
        # 先处理进程ID
        for item in pid_items:
            process_id_str = self.task_tree.item(item, 'text')
            try:
                process_id = int(process_id_str)
                is_running = psutil.pid_exists(process_id)
                
                if is_running:
                    self.task_tree.item(item, values=('进程ID', '运行中', current_time))
                    terminated_count = self.terminate_process_by_id(process_id)
                    total_terminated += terminated_count
                    
                    if terminated_count > 0:
                        self.log_task(f"成功结束进程ID: {process_id}")
                    else:
                        self.log_task(f"无法结束进程ID: {process_id}")
                else:
                    self.task_tree.item(item, values=('进程ID', '已关闭', current_time))
                    self.log_task(f"进程ID {process_id} 已关闭")
            except ValueError:
                self.task_tree.item(item, values=('进程ID', '无效ID', current_time))
                self.log_task(f"无效的进程ID: {process_id_str}")
        
        # 再处理进程名
        for item in name_items:
            process_name = self.task_tree.item(item, 'text')
            values = self.task_tree.item(item, 'values')
            process_type = values[0] if values else '进程名'
            
            is_running = self.is_process_running(process_name)
            
            if is_running:
                self.task_tree.item(item, values=(process_type, '运行中', current_time))
                terminated_count = self.terminate_process(process_name)
                total_terminated += terminated_count
                
                remaining = self.count_processes(process_name)
                if remaining > 0:
                    self.log_task(f"'{process_name}' 还有 {remaining} 个进程，尝试强制结束...")
                    force_count = self.force_kill_process(process_name)
                    total_terminated += force_count
                    self.log_task(f"强制结束 {force_count} 个 '{process_name}' 进程")
            else:
                self.task_tree.item(item, values=(process_type, '已关闭', current_time))
                self.log_task(f"进程 '{process_name}' 已关闭")
        
        # 统计本次扫描结果
        self.log_task(f"扫描完成 - 本次共结束 {total_terminated} 个进程")
    
    def is_process_running(self, process_name):
        """检查进程是否在运行"""
        try:
            for proc in psutil.process_iter(['name']):
                if proc.info['name'].lower() == process_name.lower():
                    return True
            return False
        except Exception:
            return False
    
    def count_processes(self, process_name):
        """统计指定进程名的进程数量"""
        try:
            count = 0
            for proc in psutil.process_iter(['name']):
                if proc.info['name'].lower() == process_name.lower():
                    count += 1
            return count
        except Exception:
            return 0
    
    def terminate_process_by_id(self, process_id):
        """根据进程ID结束进程，返回结束的进程数量"""
        try:
            if not psutil.pid_exists(process_id):
                self.log_task(f"进程ID {process_id} 不存在")
                return 0
            
            self.log_task(f"正在结束进程ID: {process_id}")
            
            try:
                # 使用psutil结束进程
                proc = psutil.Process(process_id)
                proc.terminate()
                self.log_task(f"发送终止信号到进程ID: {process_id}")
            except psutil.AccessDenied:
                self.log_task(f"权限不足，尝试其他方法 PID: {process_id}")
            except psutil.NoSuchProcess:
                self.log_task(f"进程ID {process_id} 已不存在")
                return 1
            
            time.sleep(0.2)
            
            # 检查是否成功结束
            if psutil.pid_exists(process_id):
                try:
                    # 使用taskkill强制结束
                    result = subprocess.run(['taskkill', '/F', '/PID', str(process_id)], 
                                          capture_output=True, text=True, timeout=5)
                    if result.returncode == 0:
                        self.log_task(f"使用taskkill成功结束进程ID: {process_id}")
                    else:
                        self.log_task(f"taskkill失败 PID: {process_id}: {result.stderr}")
                except Exception as e:
                    self.log_task(f"taskkill出错 PID: {process_id}: {e}")
                
                time.sleep(0.3)
                
                # 最后尝试Windows API
                if psutil.pid_exists(process_id):
                    try:
                        handle = win32api.OpenProcess(win32con.PROCESS_TERMINATE, False, process_id)
                        win32api.TerminateProcess(handle, 0)
                        win32api.CloseHandle(handle)
                        self.log_task(f"使用Windows API强制结束进程ID: {process_id}")
                    except Exception as e:
                        self.log_task(f"Windows API结束失败 PID: {process_id}: {e}")
                        return 0
            
            # 最终检查
            if not psutil.pid_exists(process_id):
                self.log_task(f"成功结束进程ID: {process_id}")
                return 1
            else:
                self.log_task(f"无法结束进程ID: {process_id}")
                return 0
                
        except Exception as e:
            self.log_task(f"结束进程ID时出错: {e}")
            return 0
    
    def terminate_process(self, process_name):
        """结束指定名称的进程，返回结束的进程数量"""
        try:
            # 统计结束前的进程数量
            before_count = self.count_processes(process_name)
            self.log_task(f"'{process_name}' 当前进程数量: {before_count}")
            
            terminated_count = 0
            processes_to_kill = []
            
            # 收集所有匹配的进程
            for proc in psutil.process_iter(['name', 'pid']):
                if proc.info['name'].lower() == process_name.lower():
                    processes_to_kill.append(proc)
            
            # 尝试多种方法结束进程
            for proc in processes_to_kill:
                try:
                    pid = proc.info['pid']
                    self.log_task(f"正在结束进程: {proc.info['name']} (PID: {pid})")
                    
                    # 方法1: 使用psutil的terminate()
                    try:
                        proc.terminate()
                        self.log_task(f"发送终止信号到 PID: {pid}")
                    except psutil.AccessDenied:
                        self.log_task(f"权限不足，尝试其他方法 PID: {pid}")
                    
                    # 等待一下看是否成功
                    time.sleep(0.2)
                    
                    # 检查进程是否还存在
                    if psutil.pid_exists(pid):
                        # 方法2: 使用taskkill命令
                        try:
                            result = subprocess.run(['taskkill', '/F', '/PID', str(pid)], 
                                                  capture_output=True, text=True, timeout=5)
                            if result.returncode == 0:
                                self.log_task(f"使用taskkill成功结束 PID: {pid}")
                            else:
                                self.log_task(f"taskkill失败 PID: {pid}: {result.stderr}")
                        except Exception as e:
                            self.log_task(f"taskkill出错 PID: {pid}: {e}")
                    
                    # 再次检查
                    time.sleep(0.3)
                    if not psutil.pid_exists(pid):
                        terminated_count += 1
                        self.log_task(f"成功结束进程 PID: {pid}")
                    else:
                        self.log_task(f"无法结束进程 PID: {pid}")
                        
                except psutil.NoSuchProcess:
                    terminated_count += 1
                    self.log_task(f"进程已不存在 PID: {pid}")
                except Exception as e:
                    self.log_task(f"结束进程出错 PID: {pid}: {e}")
            
            # 统计结束后的进程数量
            after_count = self.count_processes(process_name)
            
            if terminated_count > 0:
                self.log_task(f"成功结束 {terminated_count} 个 '{process_name}' 进程")
                self.log_task(f"'{process_name}' 剩余进程数量: {after_count}")
            else:
                self.log_task(f"未找到或无法结束 '{process_name}' 进程")
            
            return terminated_count
                
        except Exception as e:
            self.log_task(f"结束进程时出错: {e}")
            return 0
    
    def force_kill_process(self, process_name):
        """使用Windows API强制结束进程"""
        try:
            terminated_count = 0
            
            for proc in psutil.process_iter(['name', 'pid']):
                if proc.info['name'].lower() == process_name.lower():
                    pid = proc.info['pid']
                    try:
                        # 使用Windows API强制结束进程
                        handle = win32api.OpenProcess(win32con.PROCESS_TERMINATE, False, pid)
                        win32api.TerminateProcess(handle, 0)
                        win32api.CloseHandle(handle)
                        terminated_count += 1
                        self.log_task(f"使用Windows API强制结束 PID: {pid}")
                    except Exception as e:
                        self.log_task(f"Windows API结束失败 PID: {pid}: {e}")
            
            return terminated_count
        except Exception as e:
            self.log_task(f"强制结束进程出错: {e}")
            return 0
    
    def terminate_selected_process(self):
        """立即结束选中的进程"""
        selection = self.task_tree.selection()
        if not selection:
            messagebox.showwarning("警告", "请选择要结束的进程")
            return
        
        for item in selection:
            process_name = self.task_tree.item(item, 'text')
            values = self.task_tree.item(item, 'values')
            process_type = values[0] if values else '进程名'
            
            self.log_task(f"手动结束进程: {process_name} ({process_type})")
            
            if process_type == '进程ID':
                try:
                    process_id = int(process_name)
                    count = self.terminate_process_by_id(process_id)
                    self.log_task(f"手动结束进程ID: {process_id}, 结果: {count}")
                except ValueError:
                    self.log_task(f"无效的进程ID: {process_name}")
            else:
                # 先尝试普通方法
                count1 = self.terminate_process(process_name)
                
                # 如果还有剩余进程，使用强制方法
                remaining = self.count_processes(process_name)
                if remaining > 0:
                    self.log_task(f"还有 {remaining} 个进程，尝试强制结束...")
                    count2 = self.force_kill_process(process_name)
                    self.log_task(f"强制结束 {count2} 个进程")
    
    def terminate_all_processes(self):
        """结束所有监控的进程"""
        if not self.task_processes:
            messagebox.showwarning("警告", "没有要结束的进程")
            return
        
        result = messagebox.askyesno("确认", f"确定要结束所有 {len(self.task_processes)} 个进程吗？")
        if result:
            # 先结束进程ID类型的
            for task_item in self.task_processes:
                if task_item['type'] == 'pid':
                    process_id_str = task_item['name']
                    self.log_task(f"批量结束进程ID: {process_id_str}")
                    try:
                        process_id = int(process_id_str)
                        self.terminate_process_by_id(process_id)
                    except ValueError:
                        self.log_task(f"无效的进程ID: {process_id_str}")
            
            # 再结束进程名类型的
            for task_item in self.task_processes:
                if task_item['type'] == 'name':
                    process_name = task_item['name']
                    self.log_task(f"批量结束进程: {process_name}")
                    count1 = self.terminate_process(process_name)
                    remaining = self.count_processes(process_name)
                    if remaining > 0:
                        self.log_task(f"还有 {remaining} 个进程，尝试强制结束...")
                        count2 = self.force_kill_process(process_name)
                        self.log_task(f"强制结束 {count2} 个进程")
    
    def log_task(self, message):
        """记录任务日志"""
        timestamp = time.strftime("%H:%M:%S")
        log_message = f"[{timestamp}] {message}\n"
        
        # 在主线程中更新UI
        self.root.after(0, lambda: self.task_log.insert(tk.END, log_message))
        self.root.after(0, lambda: self.task_log.see(tk.END))
    
    def run(self):
        """运行GUI"""
        # 绑定窗口关闭事件
        self.root.protocol("WM_DELETE_WINDOW", self.on_closing)
        
        # 设置窗口最小化时隐藏到托盘
        self._setup_minimize_to_tray()
        
        # 开始检查退出信号
        self._check_exit_signal()
        
        self.root.mainloop()
    
    def _check_exit_signal(self):
        """定期检查退出信号文件"""
        try:
            # 检查退出信号文件是否存在
            if os.path.exists(self._exit_signal_file):
                debug_log("[EXIT] Exit signal file detected, shutting down...")
                try:
                    # 删除信号文件
                    os.remove(self._exit_signal_file)
                except Exception as e:
                    debug_log(f"[EXIT] Error removing signal file: {e}")
                
                # 正常退出
                self.on_closing()
                return
            
            # 每500ms检查一次
            self.root.after(500, self._check_exit_signal)
        except Exception as e:
            debug_log(f"[EXIT] Error checking exit signal: {e}")
            # 即使出错也继续检查
            self.root.after(500, self._check_exit_signal)
    
    def _setup_minimize_to_tray(self):
        """设置窗口最小化到托盘"""
        # 使用Tkinter事件监听窗口状态变化
        def on_state_change(event=None):
            """窗口状态改变时的处理"""
            try:
                if event and event.widget == self.root:
                    state = self.root.state()
                    debug_log(f"[MINIMIZE] Window state changed: {state}")
                    # 如果窗口被最小化，且未隐藏到托盘，则隐藏到托盘
                    if state == 'iconic' and self.tray_manager and not self.tray_manager.is_hidden():
                        debug_log("[MINIMIZE] Window minimized, hiding to tray")
                        self.root.after(100, lambda: self.tray_manager.hide_to_tray() if self.tray_manager else None)
            except Exception as e:
                debug_log(f"[MINIMIZE] Error in on_state_change: {e}")
        
        # 绑定窗口状态改变事件
        self.root.bind('<Map>', lambda e: debug_log(f"[MINIMIZE] Window mapped, state: {self.root.state()}"))
        self.root.bind('<Unmap>', lambda e: self._on_window_unmap(e))
        self.root.bind('<FocusOut>', lambda e: self._check_if_minimized())
        
        debug_log("[MINIMIZE] Minimize to tray handler installed")
    
    def _on_window_unmap(self, event):
        """窗口被取消映射（最小化）时的处理"""
        try:
            if event and event.widget == self.root:
                # 检查是否是通过withdraw隐藏的，如果是，不处理
                if self.tray_manager and self.tray_manager.is_hidden():
                    return
                
                # 如果窗口被最小化，隐藏到托盘
                state = self.root.state()
                debug_log(f"[MINIMIZE] Window unmapped, state: {state}")
                if state == 'iconic' and self.tray_manager:
                    debug_log("[MINIMIZE] Window minimized, hiding to tray")
                    self.root.after(100, lambda: self.tray_manager.hide_to_tray() if self.tray_manager else None)
        except Exception as e:
            debug_log(f"[MINIMIZE] Error in _on_window_unmap: {e}")
    
    def _check_if_minimized(self):
        """检查窗口是否被最小化（后备方案）"""
        try:
            if self.tray_manager and not self.tray_manager.is_hidden():
                state = self.root.state()
                debug_log(f"[MINIMIZE] Focus lost, checking state: {state}")
                if state == 'iconic':
                    debug_log("[MINIMIZE] Window is iconic, hiding to tray")
                    self.root.after(100, lambda: self.tray_manager.hide_to_tray() if self.tray_manager else None)
        except Exception as e:
            debug_log(f"[MINIMIZE] Error checking minimize state: {e}")
    
    def _acquire_lock(self):
        """获取单例锁"""
        current_pid = os.getpid()
        debug_log(f"[SINGLETON][PID:{current_pid}] 尝试获取单例锁")
        try:
            # 在Windows上使用文件锁
            if sys.platform == 'win32':
                import msvcrt
                # 尝试创建并锁定文件
                if os.path.exists(self._lock_file):
                    # 检查锁文件是否有效（进程是否还在运行）
                    try:
                        with open(self._lock_file, 'r') as f:
                            pid = int(f.read().strip())
                        # 检查进程是否存在
                        try:
                            import psutil
                            if psutil.pid_exists(pid):
                                proc = psutil.Process(pid)
                                # 检查进程名是否匹配
                                proc_name = proc.name().lower()
                                if 'windowspygui' in proc_name or 'python' in proc_name:
                                    cmdline = proc.cmdline()
                                    if any('window_spy' in str(cmd) or 'WindowSpyGUI' in str(cmd) for cmd in cmdline):
                                        debug_log(f"[SINGLETON][PID:{current_pid}] 发现已有实例在运行，PID: {pid}")
                                        # 显示已存在的窗口
                                        self._show_existing_window()
                                        # 实例正在运行，返回False
                                        return False
                        except (ImportError, psutil.NoSuchProcess, psutil.AccessDenied):
                            # 如果进程不存在或无法访问，删除旧锁文件
                            try:
                                os.remove(self._lock_file)
                                debug_log(f"[SINGLETON][PID:{current_pid}] 删除无效的锁文件")
                            except:
                                debug_log(f"[SINGLETON][PID:{current_pid}] 无法删除无效的锁文件")
                                pass
                    except (ValueError, FileNotFoundError):
                        # 锁文件无效，删除它
                        try:
                            os.remove(self._lock_file)
                            debug_log(f"[SINGLETON][PID:{current_pid}] 删除无效的锁文件")
                        except:
                            debug_log(f"[SINGLETON][PID:{current_pid}] 无法删除无效的锁文件")
                            pass
                
                # 创建锁文件
                try:
                    self._lock_handle = open(self._lock_file, 'w')
                    self._lock_handle.write(str(current_pid))
                    self._lock_handle.flush()
                    # 尝试锁定文件
                    msvcrt.locking(self._lock_handle.fileno(), msvcrt.LK_NBLCK, 1)
                    debug_log(f"[SINGLETON][PID:{current_pid}] 成功获取单例锁")
                    return True
                except IOError as e:
                    # 无法获取锁，说明已有实例在运行
                    debug_log(f"[SINGLETON][PID:{current_pid}] 无法获取单例锁，可能已有实例运行: {e}")
                    # 显示已存在的窗口
                    self._show_existing_window()
                    if self._lock_handle:
                        self._lock_handle.close()
                        self._lock_handle = None
                    return False
            else:
                # 非Windows系统，使用简单的文件存在检查
                if os.path.exists(self._lock_file):
                    try:
                        with open(self._lock_file, 'r') as f:
                            pid = int(f.read().strip())
                        import psutil
                        if psutil.pid_exists(pid):
                            debug_log(f"[SINGLETON][PID:{current_pid}] 发现已有实例在运行，PID: {pid}")
                            # 显示已存在的窗口
                            self._show_existing_window()
                            return False
                    except:
                        pass
                
                # 创建锁文件
                with open(self._lock_file, 'w') as f:
                    f.write(str(current_pid))
                debug_log(f"[SINGLETON][PID:{current_pid}] 成功获取单例锁（非Windows）")
                return True
        except Exception as e:
            debug_log(f"[SINGLETON][PID:{current_pid}] 获取单例锁时发生错误: {type(e).__name__}: {e}")
            # 显示已存在的窗口
            self._show_existing_window()
            # 出错时不应该允许运行多个实例，返回False
            return False
    
    def _release_lock(self):
        """释放单例锁"""
        try:
            if self._lock_handle:
                try:
                    if sys.platform == 'win32':
                        import msvcrt
                        msvcrt.locking(self._lock_handle.fileno(), msvcrt.LK_UNLCK, 1)
                except:
                    pass
                self._lock_handle.close()
                self._lock_handle = None
            
            if os.path.exists(self._lock_file):
                os.remove(self._lock_file)
        except Exception as e:
            debug_log(f"[SINGLETON] Error releasing lock: {e}")
    
    def add_guardian_process(self):
        """添加守护进程"""
        dialog = tk.Toplevel(self.root)
        dialog.title("添加守护进程")
        dialog.geometry("500x250")
        dialog.transient(self.root)
        dialog.grab_set()
        
        main_frame = ttk.Frame(dialog, padding="20")
        main_frame.pack(fill=tk.BOTH, expand=True)
        
        # 程序路径
        ttk.Label(main_frame, text="程序路径:").grid(row=0, column=0, sticky=tk.W, pady=5)
        path_var = tk.StringVar()
        path_entry = ttk.Entry(main_frame, textvariable=path_var, width=40)
        path_entry.grid(row=0, column=1, sticky=tk.W, padx=5, pady=5)
        
        def browse_file():
            filename = filedialog.askopenfilename(
                title="选择程序",
                filetypes=[("可执行文件", "*.exe"), ("所有文件", "*.*")]
            )
            if filename:
                path_var.set(filename)
        
        ttk.Button(main_frame, text="浏览...", command=browse_file).grid(row=0, column=2, padx=5, pady=5)
        
        # 命令行参数
        ttk.Label(main_frame, text="命令行参数:").grid(row=1, column=0, sticky=tk.W, pady=5)
        args_var = tk.StringVar()
        ttk.Entry(main_frame, textvariable=args_var, width=40).grid(row=1, column=1, columnspan=2, sticky=tk.W, padx=5, pady=5)
        
        # 检查间隔
        ttk.Label(main_frame, text="检查间隔(秒):").grid(row=2, column=0, sticky=tk.W, pady=5)
        interval_var = tk.IntVar(value=60)
        ttk.Spinbox(main_frame, from_=10, to=3600, textvariable=interval_var, width=38).grid(row=2, column=1, sticky=tk.W, padx=5, pady=5)
        
        def save():
            path = path_var.get().strip()
            if not path:
                messagebox.showwarning("警告", "请输入程序路径", parent=dialog)
                return
            
            process_info = {
                'path': path,
                'args': args_var.get().strip(),
                'interval': interval_var.get()
            }
            
            self.guardian_tree.insert('', 'end', values=(path, process_info['args'], process_info['interval']))
            self.guardian_processes.append(process_info)
            self.save_config()
            dialog.destroy()
        
        btn_frame = ttk.Frame(main_frame)
        btn_frame.grid(row=3, column=0, columnspan=3, pady=20)
        ttk.Button(btn_frame, text="保存", command=save).pack(side=tk.LEFT, padx=5)
        ttk.Button(btn_frame, text="取消", command=dialog.destroy).pack(side=tk.LEFT, padx=5)
    
    def edit_guardian_process(self):
        """编辑守护进程"""
        selection = self.guardian_tree.selection()
        if not selection:
            messagebox.showwarning("警告", "请先选择一个进程")
            return
        
        item = selection[0]
        index = self.guardian_tree.index(item)
        process_info = self.guardian_processes[index]
        
        dialog = tk.Toplevel(self.root)
        dialog.title("编辑守护进程")
        dialog.geometry("500x250")
        dialog.transient(self.root)
        dialog.grab_set()
        
        main_frame = ttk.Frame(dialog, padding="20")
        main_frame.pack(fill=tk.BOTH, expand=True)
        
        # 程序路径
        ttk.Label(main_frame, text="程序路径:").grid(row=0, column=0, sticky=tk.W, pady=5)
        path_var = tk.StringVar(value=process_info['path'])
        path_entry = ttk.Entry(main_frame, textvariable=path_var, width=40)
        path_entry.grid(row=0, column=1, sticky=tk.W, padx=5, pady=5)
        
        def browse_file():
            filename = filedialog.askopenfilename(
                title="选择程序",
                filetypes=[("可执行文件", "*.exe"), ("所有文件", "*.*")]
            )
            if filename:
                path_var.set(filename)
        
        ttk.Button(main_frame, text="浏览...", command=browse_file).grid(row=0, column=2, padx=5, pady=5)
        
        # 命令行参数
        ttk.Label(main_frame, text="命令行参数:").grid(row=1, column=0, sticky=tk.W, pady=5)
        args_var = tk.StringVar(value=process_info.get('args', ''))
        ttk.Entry(main_frame, textvariable=args_var, width=40).grid(row=1, column=1, columnspan=2, sticky=tk.W, padx=5, pady=5)
        
        # 检查间隔
        ttk.Label(main_frame, text="检查间隔(秒):").grid(row=2, column=0, sticky=tk.W, pady=5)
        interval_var = tk.IntVar(value=process_info.get('interval', 60))
        ttk.Spinbox(main_frame, from_=10, to=3600, textvariable=interval_var, width=38).grid(row=2, column=1, sticky=tk.W, padx=5, pady=5)
        
        def save():
            path = path_var.get().strip()
            if not path:
                messagebox.showwarning("警告", "请输入程序路径", parent=dialog)
                return
            
            updated_info = {
                'path': path,
                'args': args_var.get().strip(),
                'interval': interval_var.get()
            }
            
            self.guardian_tree.item(item, values=(path, updated_info['args'], updated_info['interval']))
            self.guardian_processes[index] = updated_info
            self.save_config()
            dialog.destroy()
        
        btn_frame = ttk.Frame(main_frame)
        btn_frame.grid(row=3, column=0, columnspan=3, pady=20)
        ttk.Button(btn_frame, text="保存", command=save).pack(side=tk.LEFT, padx=5)
        ttk.Button(btn_frame, text="取消", command=dialog.destroy).pack(side=tk.LEFT, padx=5)
    
    def delete_guardian_process(self):
        """删除守护进程"""
        selection = self.guardian_tree.selection()
        if not selection:
            messagebox.showwarning("警告", "请先选择一个进程")
            return
        
        if messagebox.askyesno("确认", "确定要删除选中的守护进程吗?"):
            item = selection[0]
            index = self.guardian_tree.index(item)
            self.guardian_tree.delete(item)
            del self.guardian_processes[index]
            self.save_config()
    
    def log_guardian(self, message):
        """记录守护日志"""
        try:
            timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
            self.guardian_log.insert(tk.END, f"[{timestamp}] {message}\n")
            self.guardian_log.see(tk.END)
        except Exception:
            pass
    
    def start_guardian(self):
        """开始守护"""
        if not self.guardian_processes:
            messagebox.showwarning("警告", "请先添加要守护的进程")
            return
        
        if self.guardian_running:
            return
        
        self.guardian_running = True
        self.guardian_start_btn.config(state=tk.DISABLED)
        self.guardian_stop_btn.config(state=tk.NORMAL)
        
        self.log_guardian("守护服务已启动")
        
        # 启动守护线程
        self.guardian_thread = threading.Thread(target=self._guardian_monitor_loop, daemon=True)
        self.guardian_thread.start()
    
    def stop_guardian(self):
        """停止守护"""
        if not self.guardian_running:
            return
        
        self.guardian_running = False
        self.guardian_start_btn.config(state=tk.NORMAL)
        self.guardian_stop_btn.config(state=tk.DISABLED)
        
        self.log_guardian("守护服务已停止")
    
    def _guardian_monitor_loop(self):
        """守护监控循环"""
        while self.guardian_running:
            try:
                for process_info in self.guardian_processes:
                    if not self.guardian_running:
                        break
                    
                    path = process_info['path']
                    args = process_info.get('args', '')
                    
                    # 检查进程是否在运行
                    process_name = os.path.basename(path)
                    is_running = False
                    
                    for proc in psutil.process_iter(['name', 'exe']):
                        try:
                            if proc.info['name'] and proc.info['name'].lower() == process_name.lower():
                                is_running = True
                                break
                            if proc.info['exe'] and proc.info['exe'].lower() == path.lower():
                                is_running = True
                                break
                        except (psutil.NoSuchProcess, psutil.AccessDenied):
                            continue
                    
                    if not is_running:
                        # 进程未运行，启动它
                        try:
                            if args:
                                subprocess.Popen([path] + args.split(), shell=False)
                            else:
                                subprocess.Popen([path], shell=False)
                            self.log_guardian(f"已启动进程: {process_name}")
                        except Exception as e:
                            self.log_guardian(f"启动进程失败 {process_name}: {e}")
                
                # 使用全局检查间隔
                interval = self.guardian_interval_var.get()
                time.sleep(interval)
                
            except Exception as e:
                self.log_guardian(f"监控出错: {e}")
                time.sleep(10)
    
    def search_port(self):
        """根据端口号查询进程"""
        port_str = self.port_entry.get().strip()
        if not port_str:
            messagebox.showwarning("警告", "请输入端口号")
            return
        
        try:
            port = int(port_str)
        except ValueError:
            messagebox.showerror("错误", "端口号必须是数字")
            return
        
        # 清空现有结果
        for item in self.port_tree.get_children():
            self.port_tree.delete(item)
        
        # 查询端口
        count = 0
        try:
            connections = psutil.net_connections(kind='inet')
            for conn in connections:
                if conn.laddr.port == port:
                    try:
                        # 获取进程信息
                        if conn.pid:
                            proc = psutil.Process(conn.pid)
                            proc_name = proc.name()
                        else:
                            proc_name = "System"
                        
                        # 协议类型
                        protocol = "TCP" if conn.type == 1 else "UDP"
                        
                        # 本地地址
                        local_addr = f"{conn.laddr.ip}:{conn.laddr.port}"
                        
                        # 远程地址
                        if conn.raddr:
                            remote_addr = f"{conn.raddr.ip}:{conn.raddr.port}"
                        else:
                            remote_addr = "-"
                        
                        # 状态
                        status = conn.status if conn.status else "-"
                        
                        self.port_tree.insert('', 'end', values=(
                            proc_name,
                            conn.pid if conn.pid else "-",
                            protocol,
                            local_addr,
                            remote_addr,
                            status
                        ))
                        count += 1
                    except (psutil.NoSuchProcess, psutil.AccessDenied):
                        continue
            
            self.port_stats_label.config(text=f"找到 {count} 个连接使用端口 {port}")
            
            if count == 0:
                messagebox.showinfo("提示", f"没有找到使用端口 {port} 的进程")
        
        except Exception as e:
            messagebox.showerror("错误", f"查询失败: {e}")
    
    def search_all_ports(self):
        """查询所有端口使用情况"""
        # 清空现有结果
        for item in self.port_tree.get_children():
            self.port_tree.delete(item)
        
        try:
            connections = psutil.net_connections(kind='inet')
            count = 0
            
            for conn in connections:
                try:
                    # 获取进程信息
                    if conn.pid:
                        proc = psutil.Process(conn.pid)
                        proc_name = proc.name()
                    else:
                        proc_name = "System"
                    
                    # 协议类型
                    protocol = "TCP" if conn.type == 1 else "UDP"
                    
                    # 本地地址
                    local_addr = f"{conn.laddr.ip}:{conn.laddr.port}"
                    
                    # 远程地址
                    if conn.raddr:
                        remote_addr = f"{conn.raddr.ip}:{conn.raddr.port}"
                    else:
                        remote_addr = "-"
                    
                    # 状态
                    status = conn.status if conn.status else "-"
                    
                    self.port_tree.insert('', 'end', values=(
                        proc_name,
                        conn.pid if conn.pid else "-",
                        protocol,
                        local_addr,
                        remote_addr,
                        status
                    ))
                    count += 1
                except (psutil.NoSuchProcess, psutil.AccessDenied):
                    continue
            
            self.port_stats_label.config(text=f"共找到 {count} 个网络连接")
        
        except Exception as e:
            messagebox.showerror("错误", f"查询失败: {e}")
    
    def search_process_ports(self):
        """根据进程名查询端口使用情况"""
        process_name = self.process_name_entry.get().strip()
        if not process_name:
            messagebox.showwarning("警告", "请输入进程名")
            return
        
        # 清空现有结果
        for item in self.port_tree.get_children():
            self.port_tree.delete(item)
        
        try:
            # 查找匹配的进程
            matching_pids = []
            for proc in psutil.process_iter(['name', 'pid']):
                try:
                    if proc.info['name'] and process_name.lower() in proc.info['name'].lower():
                        matching_pids.append(proc.info['pid'])
                except (psutil.NoSuchProcess, psutil.AccessDenied):
                    continue
            
            if not matching_pids:
                messagebox.showinfo("提示", f"没有找到进程名包含 '{process_name}' 的进程")
                self.port_stats_label.config(text="")
                return
            
            # 查询这些进程使用的端口
            connections = psutil.net_connections(kind='inet')
            count = 0
            port_set = set()
            
            for conn in connections:
                if conn.pid in matching_pids:
                    try:
                        # 获取进程信息
                        proc = psutil.Process(conn.pid)
                        proc_name = proc.name()
                        
                        # 协议类型
                        protocol = "TCP" if conn.type == 1 else "UDP"
                        
                        # 本地地址
                        local_addr = f"{conn.laddr.ip}:{conn.laddr.port}"
                        port_set.add(conn.laddr.port)
                        
                        # 远程地址
                        if conn.raddr:
                            remote_addr = f"{conn.raddr.ip}:{conn.raddr.port}"
                        else:
                            remote_addr = "-"
                        
                        # 状态
                        status = conn.status if conn.status else "-"
                        
                        self.port_tree.insert('', 'end', values=(
                            proc_name,
                            conn.pid,
                            protocol,
                            local_addr,
                            remote_addr,
                            status
                        ))
                        count += 1
                    except (psutil.NoSuchProcess, psutil.AccessDenied):
                        continue
            
            self.port_stats_label.config(
                text=f"找到 {len(matching_pids)} 个进程, 使用 {len(port_set)} 个端口, 共 {count} 个连接"
            )
            
            if count == 0:
                messagebox.showinfo("提示", f"进程 '{process_name}' 没有使用任何端口")
        
        except Exception as e:
            messagebox.showerror("错误", f"查询失败: {e}")
    
    def clear_port_results(self):
        """清空端口查询结果"""
        for item in self.port_tree.get_children():
            self.port_tree.delete(item)
        self.port_stats_label.config(text="")
        self.port_entry.delete(0, tk.END)
        self.process_name_entry.delete(0, tk.END)
    
    def show_port_context_menu(self, event):
        """显示端口右键菜单"""
        item = self.port_tree.identify_row(event.y)
        if item:
            self.port_tree.selection_set(item)
            self.port_context_menu.post(event.x_root, event.y_root)
    
    def copy_port_info(self, column):
        """复制端口信息"""
        selection = self.port_tree.selection()
        if not selection:
            return
        
        item = selection[0]
        values = self.port_tree.item(item, 'values')
        
        column_index = {
            '进程名': 0,
            'PID': 1,
            '协议': 2,
            '本地地址': 3,
            '远程地址': 4,
            '状态': 5
        }
        
        if column in column_index:
            text = values[column_index[column]]
            self.root.clipboard_clear()
            self.root.clipboard_append(text)
            messagebox.showinfo("成功", f"已复制: {text}")
    
    def kill_port_process(self):
        """结束端口对应的进程"""
        selection = self.port_tree.selection()
        if not selection:
            return
        
        item = selection[0]
        values = self.port_tree.item(item, 'values')
        pid = values[1]
        proc_name = values[0]
        
        if pid == "-" or pid == "System":
            messagebox.showwarning("警告", "无法结束系统进程")
            return
        
        if messagebox.askyesno("确认", f"确定要结束进程 {proc_name} (PID: {pid}) 吗?"):
            try:
                proc = psutil.Process(int(pid))
                proc.terminate()
                messagebox.showinfo("成功", f"已发送终止信号给进程 {proc_name}")
                # 刷新列表
                port_str = self.port_entry.get().strip()
                if port_str:
                    self.search_port()
                else:
                    self.search_all_ports()
            except Exception as e:
                messagebox.showerror("错误", f"结束进程失败: {e}")
    
    def on_closing(self):
        """程序关闭时的处理"""
        # 停止守护服务
        if self.guardian_running:
            self.stop_guardian()
            if self.guardian_thread:
                self.guardian_thread.join(timeout=2)
        
        # 清理退出信号文件
        try:
            if hasattr(self, '_exit_signal_file') and os.path.exists(self._exit_signal_file):
                os.remove(self._exit_signal_file)
        except Exception:
            pass
        
        # 释放单例锁
        self._release_lock()
        
        # 注销全局热键
        try:
            import keyboard
            if getattr(self, "_hotkey_registered", False):
                keyboard.remove_hotkey("ctrl+alt+i")
                try:
                    keyboard.remove_hotkey("ctrl+alt+shift+i")
                except:
                    pass
        except Exception:
            pass
        
        # 停止任务监控
        if self.task_running:
            self.stop_task_monitoring()
        
        # 保存配置
        self.save_config()
        
        # 关闭窗口
        self.root.destroy()


def main():
    """主函数"""
    import argparse
    
    # 解析命令行参数
    parser = argparse.ArgumentParser(description="Windows Process Spy - 窗口句柄检测工具")
    parser.add_argument('--exit', action='store_true', help='发送退出信号给正在运行的实例')
    args = parser.parse_args()
    
    # 如果指定了 --exit 参数，创建退出信号文件
    if args.exit:
        try:
            import tempfile
            # 使用临时目录存放退出信号文件
            temp_dir = tempfile.gettempdir()
            exit_signal_file = os.path.join(temp_dir, "windowspygui_exit_signal.txt")
            with open(exit_signal_file, 'w') as f:
                f.write("exit")
            print(f"退出信号已发送: {exit_signal_file}")
            print("程序将在几秒内退出...")
            return
        except Exception as e:
            print(f"发送退出信号失败: {e}")
            return
    
    # 正常运行程序
    try:
        app = WindowSpyGUI()
        app.run()
    except Exception as e:
        print(f"程序运行出错: {e}")
        try:
            messagebox.showerror("错误", f"程序运行出错: {e}")
        except:
            pass


if __name__ == "__main__":
    main()
